What is the difference between router and route in Emberjs - ember.js

What is the difference between router and route in emberjs? I wanted an implementable explanation if possible

Router - ember application will have one Router, it manages transitions between routes and it contains map of all routes. You specify map of routes like so:
App.Router.map(function(){
this.route('post', { path: '/post/:post_id' }, function() {
this.route('edit');
this.route('comments', { resetNamespace: true }, function() {
this.route('new');
});
});
});
from which Router is able to identify structure of routes and their accepting params. It activates corresponding Route when you navigate to particular path/url in browser ember router docs
Route - for each path/route you will have Route object, when you change path/url in browser corresponding Route gets activated for that path and set up everything (controller, template) which relate to that route (that has same name usually).
ember route docs
read more

The practical difference between these two is that resource may contain other routes, whereas route cannot. Additionally, this.resource automatically creates an index route when it sees you have sub-routes.
App.Router.map(function() {
this.resource('records', function() {
// this.route('index') is created automatically in here
this.route('new');
});
this.route('about');
});
While you could always use this.resource and have things work correctly, this.route explicitly creates router leaf states and therefore will lead to a more precise and efficient graph of states for your app.

Related

Best practice to organize list, view, create and update routes in Ember.js

Working for a few years with ember.js now, it's still not quite clear to me, what should be considered as best practice for structuring list, view, create and update routes.
The projects I've worked with so far mostly used to routing trees per entity. The pluralized entity name for listing with a subroute for create and the singular entity name for detail view with a subroute for editing. As an example a post model would have these for routes: /posts for listing posts, /posts/new for the create functionality, /post/:post_id for showing a single post and /post/:post_id/edit for editing that one. The corresponding router would look like this one:
Router.map(function() {
this.route('post', { path: '/post/:post_id' }, function() {
this.route('edit');
});
this.route('posts', function() {
this.route('new');
});
});
This approach is working quite nicely for detail and edit view cause they are sharing the same model. So the model hook of the edit route could just reuse the model of the detail view route. In ember code this looks like the following:
// app/routes/post.js
import Route from '#ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.get('store').findRecord('post', post_id);
}
});
// app/routes/post/edit.js
import Route from '#ember/routing/route';
export default Route.extend({
model() {
return this.modelFor('post');
}
});
Normally we would return a collection of posts from posts route model hook and not implementing the model hook of posts.new route (or returning a POJO / Changeset there depending on architecture but that's not the question here). Assuming we are not implementing the model hook of posts.new the routes would look like:
// app/routes/posts.js
import Route from '#ember/routing/route';
export default Route.extend({
model({ post_id }) {
return this.get('store').findAll('post');
}
});
// app/routes/posts/new.js
import Route from '#ember/routing/route';
export default Route.extend({
});
But now this approach is not working well anymore cause a transition to posts.new route is blocked until the collection of posts are loaded. Since we don't need this collection to create a list of posts (at least if we only show them in posts.index route and not on all subroutes) this doesn't feel right.
Side note for those ones not that familiar with ember: Nested routes model hooks are executed in order. So in our case first the model hook of application route, afterwards posts route and then posts.new route waiting for any promise executed by one of them.
So what should then be considered as best practice?
Should the fetching of posts live in posts.index route if we are not showing them on nested routes?
Shouldn't the create route be a nested under the list route? So should we have posts, post-new, post and post.edit routes? Feels confusing since the post related code is splited over three route trees. Also it would go against the goal of the improved file layout being developed currently since the code would be splitted over three directories.
Should we just take the tradeoff of unnecessarily fetching the collection of posts since mostly the user flow comes from this route before the creation route and therefore the model hook is in most cases already loaded anyway?
Would appreciate any thoughts on that one. Decided to not ask that question in the community slack to better document the answer.
The main point of having a nested route in ember is to nest the output of your child route within the parent route. While your current structure works, it doesn't really match up with how ember has structured route functionality.
You should use a singular nested route with an explicitly defined index route.
At every level of nesting (including the top level), Ember
automatically provides a route for the / path named index. To see when
a new level of nesting occurs, check the router, whenever you see a
function, that's a new level.
Router.map(function() {
this.route('posts', function() {
this.route('favorites');
});
});
is equivalent to
Router.map(function() {
this.route('index', { path: '/' });
this.route('posts', function() {
this.route('index', { path: '/' });
this.route('favorites');
});
});
If you create an explicit posts/index.js file, this can be used as your list route. Doing this will help your avoid the issue where all posts are fetched before transitioning into the create route.
While different from the structure you currently have, I'd suggest the following.
Router.map(function() {
this.route('posts', function() {
this.route('index'); // /posts - posts/index.js
this.route('new'); // /posts/new - posts/new.js
this.route('view', { path: '/:post_id' } // /posts/1234 - posts/view.js
this.route('edit', { path: '/:post_id/edit' } // /posts/1234/edit - posts/edit.js
});
});
Depending on the complexity of logic in the new and edit, you can consider combining the two routes into one, or simply transitioning the new to edit after generating the empty model.
The benefits of this include:
Simplicity
You don't have to re-define your paths for all of the routes. Everything falls under posts/ and the route specifies the next piece.
Consistency
the JSONapi schema uses plural routes for both fetching a collection as well as a singular object.
Logic wrapping
If, you use and explicit index.js file, you can use the old posts.js file as a common wrapper for all items within the post namespace. Posts.js will have an outlet that the index, new, edit, and view routes are placed into
If you need your view and edit route to share the same model generation, you can nest your view/edit into a common group so they share a parent model.
this.route('posts', function() {
this.route('post', { path: '/:post_id' }, function() {
this.route('view', { path: '/' }) ;
this.route('edit', { path: '/edit' });
})
})

Ember route with dynamic segment name

I'm just starting with Ember JS and Ember CLI and trying to figure out this routing issue. I have a group model that has many game models. With the following route, I am able to display games just fine from a group URL:
Router.map(function() {
this.resource("groups", function() {
this.route('show', {path: ':group_id/show' });
});
});
This results in a URL with the form:
http://localhost:4200/groups/1/show
Suppose one of the group names is "wizards". I'd like to to be able to construct a URL in the following form and render all the games that belong to "wizards":
http://localhost:4200/wizards
Any tips are appreciated.
Like #blessenm points out in the comments, your router would change from
Router.map(function() {
this.resource("groups", function() {
this.route('show', {path: ':group_id/show' });
});
});
to
Router.map(function() {
this.resource("group", { path: ':group_name'});
});
The second parameter to this.resource() or this.route() is optional. If you don't pass anything in - it assumes the same name as your route/resource (groups, in your case). If you pass in an object that has a path: key - you are specifying what the url to the route is, including a dynamic segment. See here for Ember documentation on this.

Ember Dynamic Segment Unavailable in Route

I have the following router setup in Ember where I am trying to capture a dynamic search term and pass it to the router for querying ember-data.
Router
this.resource('resources', function() {
this.resource('resource', { path: '/:resource_id' }, function() {});
this.resource('search', { path: '/search/:search_term' }, function() {});
this.route('new');
});
Route
export default Ember.Route.extend({
model: function(params) {
return this.store.findQuery('resource', {
sTerm: params.search_term,
limit: 15,
offset: 0
});
}
});
Unfortunately, search_term in not available in the route to pass into the query, I am unsure what is causing this not to work. If someone can point me in the right direction I would sure appreciate it. Thanks.
Update as requested
Logging this.constructor produces the following:
lrs-ui#route:search/index:
I have built this with ember-cli and the route is in search/index so this makes sense. Should I just have the route at search maybe?
Answer
As #kingpin2k led to, the route was in search/index and it needed to be in search, then everything worked just fine.
For historical sake, the index route doesn't pick up the params from the parent resource.
Changing the route from search/index to search fixed the issue.

Ember router naming conventions

I have a need for deep nesting some routes in ember, I have something like this.
this.resource('wizards', {
path: '/wizards'
}, function() {
this.resource('wizards.google', {
path: '/google'
}, function() {
this.resource('wizards.google.register', {
path: '/register'
}, function() {
this.route('step1');
this.route('step2');
this.route('step3');
this.route('summary');
});
});
});
What I was expecting was as structure like this:
url /wizards/google/register/step1
route name wizards.google.register.step1
route Wizards.Google.Register.Step1Route
Controller Wizards.Google.Register.Step1Controller
template wizards/google/register/step1
but I got this:
url /wizards/google/register/step1 //as expected
route name wizards.google.register.step1 //as expected
route WizardsGoogle.Register.Step1Route
Controller WizardsGoogle.Register.Step1Controller
template wizards/google.register.step1
What I don't get is when does ember stop using capitalization (WizardsGoogle) and start using namespaces (WizardsGoogle.Register). The seemingly inconsistency confuses me. I would have expected either of them.
I met the same things with deep nested resources. Although I didn't know how this happens, what I can tell is that you can always use CapitalizedNestedRoute without namespace, and Ember can recognize it. Although in Ember Inspector it displays "WizardsGoogle.Register.Step1Route".
In your example I defined such route:
App = Em.Application.create();
App.Router.map(function() {
this.resource('wizards', function() {
this.resource('wizards.google', function() {
this.resource('wizards.google.register', function() {
this.route('step1');
this.route('step2');
this.route('step3');
});
});
});
});
App.IndexRoute = Em.Route.extend({
beforeModel: function() {
// Transition to step1 route
this.transitionTo('wizards.google.register.step1');
}
});
App.WizardsGoogleRegisterStep1Route = Em.Route.extend({
model: function() {
// You can see this alert when you enter index page.
alert('a');
}
});
In this example the app will transition to WizardsGoogleRegisterStep1Route with no problem. And if you use container to find route like this:
App.__container__.lookup('route:wizards.google.register.step1').constructor
It will also display App.WizardsGoogleRegisterStep1Route. It's the same as Ember Guide describes. http://emberjs.com/guides/routing/defining-your-routes/#toc_nested-resources And Ember Guide doesn't introduce namespace route.
So I think it's better to according to what Ember Guide suggests (always use CapitalizedNestedRoute). And in my opinion it's easier to define CapitalizedNestedRoute than nested.namespace.route.
Finally, if you really want to use namespace route/controller/template, you can have a look at Ember.DefaultResolver. Check the API to learn how to extend it so container can lookup modules by your own rules.
Routes are "namespaced" inside resources. And resources uses what you call capitalization, where they sort of define a namespace (for routes to use).
So this set of routes:
App.Router.map(function() {
this.resource('posts', function() {
this.route('new');
this.route('old');
this.route('edit');
this.route('whatever');
});
});
Would result in routes with the following name:
PostsRoute
PostsNewRoute
PostsOldRoute
PostsEditRoute
PostsWhateverRoute
Whereas, the following set of routes:
App.Router.map(function() {
this.resource('posts', function() {
this.resource('photos');
this.resource('comments');
this.resource('likes');
this.resource('teets');
});
});
Would result in route with the following names:
PostsRoute
PhotosRoute
CommentsRoute
LikesRoute
TeetsRoute
Also note, that resources within resources don't get "namespaced" to the "parent" resource, so you'll always ever have the form:
{CapitalizedResourceName}Route // for resources
{CapitalizedParentResourceName}{RouteName}Route // for routes
I hope this helps you!

Emberjs pre4 nested route default URI

Can some one explain why nested resources require to list the path hierarchy in the route name instead of just the route?
Eg. resource1 > resource1.resource2
Emberjs seems to be all about reducing the amount of code. Is there some usecase for resources I don't see which explains why resources should be defined this way.
I couldn't get my example to work in jsfiddle or jsbin so I hosted it here: http://emberjs.mattmazzola.net/
I was basing my solution from the technique described in this similar StackOverflow question is here: Ember.js pre4 multiple nested routing
Basically, you notice I have a resource 'animals' with sub resources 'cats' and 'dogs'. However, if I just name them 'cats' and 'dogs' respectively the router says "route animals.cats' is not found. Then if I add the 'animals.' prefix to make the nested route 'animals.cats' the url becomes index#/animals/animals.cats which doesn't make sense. Of course we fix this by overriding the path attribute, but I don't understand why Emberjs doesn't do this by default. Am I defining my resources/routes incorrectly and this is a side affect?
In other words, I'm currently doing this:
App.Router.map(function() {
this.resource('products', function() {
this.route('desktops');
this.route('laptops');
});
this.resource('animals', function() {
// the url for this route is bad, but default behavior?
this.resource('animals.cats', function() {
this.route('cat', {path: ':cat_id'});
});
// Why does this require stating the parent route 'animals' again?
this.resource('animals.dogs', {path: 'dogs/'}, function() {
this.route('dog', {path: ':dog_id'});
});
});
});
How can I write routes like this:
App.Router.map(function() {
this.resource('products', function() {
this.route('desktops');
this.route('laptops');
});
this.resource('animals', function() {
this.resource('cats', function() {
this.route('cat', {path: ':cat_id'});
});
this.resource('dogs', function() {
this.route('dog', {path: ':dog_id'});
});
});
});
hmm, i think the second version should work if you have App.AnimalsIndexRoute, App.CatsIndexRoute and App.DogsIndexRoute (and possibly a few other Ember.Routes) defined correctly. could you maybe post the rest of your code here or in a jsfiddle if you still have that problem?