Dear @Gowtham, can you be more specific regarding your question?
app/templates/posts.hbs
has the layout shared by all routes under /posts
.
app/templates/posts/index.hbs
has the layout of the route /posts
.
app/templates/posts/show.hbs
has the layout of the route /posts/show/:post_id
.
Hi @rgaiacs i am confused about this index.hbs template what is core main purpose of it in the context of template rendering
At every level of route nesting there is an index template/route which is the default page of that nesting level. In your case there is an application index (routes/index) and a posts index (routes/posts/index) and corresponding templates. Typically how you might structure something like this is that the index route shows a list or dashboard which links into a detail. So your posts index route and template might show the list of posts with links to the post detail page. The thing that does get confusing is that Ember creates index routes implicitly so itâs easy to forget they are there.
Thanks @dknutsen . Correct me If i am Wrong. For Now let me understand that For each Parent Route we can have one Index route if we needed. Parent Route gets First Rendered and then following Index Template also gets Render from the {{outlet}} of itâs Parent Template. Each index will have its Own RouteHandler,Test,Template File
Here i used Parent Template to Display Layout kind of Things (Which i am not SURE we need to use Parent Template for this kind of Things) and index Template to Render list or following multiple values that are associated with Parent.I might me Wrong.
The thing to remember is that if routes are nested the UI is always nested. Letâs say you have a few routes defined:
this.route(âparentâ, function() {
this.route(âchildâ, function() {
this.route(âgrandchildâ);
});
});
This means that youâll have the following routes created in the router:
- index
- parent.index
- parent.child.index
- parent.child.grandchild
Youâll have the following templates, and if you donât create them they will be automagically created for you by Ember:
- application.hbs
- index.hbs (renders inside application.hbs)
- parent.hbs (renders inside application.hbs)
- parent/index.hbs (rendered inside parent.hbs)
- parent/child.hbs (renders inside parent.hbs)
- parent/child/index.hbs (renders inside parent/child.hbs)
- parent/child/grandchild.hbs (renders inside parent/child.hbs)
So if you visited the route âparent.child.grandchildâ it would render âapplication.hbsâ first, then render âparent.hbsâ in the application outlet, then render âchild.hbsâ in the parent outlet, then render âgrandchild.hbsâ inside the child outlet
Thanks @dknutsen Now i have a clarity about index. As you said âif routes are nested the UI is always nestedâ so is here any âRules to Design the Routeâ because i think nesting multiple routes might not be a good practice?
Nesting routes isnât bad practice but it can make your code a little more complex so as with anything itâs something you only want to do if itâs helpful. There are many cases where it is helpful. Specifically if your URLs map to nested UI and/or data fetching.
Letâs look at an example to help illustrate that better, and take a look at all of the options and what their pros/cons are.
Example Requirements
Letâs say youâre building a directory of users and you want two new âmainâ pages: one index/list page to list users and filter/search/sort them, and one âdetailâ page where you can view the details of a specific user.
The detail page should have a heading with the userâs name and multiple tabs and each tab allows you to view/edit information about that user in a specific category. Letâs say the tabs are âGeneralâ, âContactâ and âPermissionsâ (no need to go deeper on requirements just use your imagination).
Letâs say it looks something like this simple example:
How you build this first depends on your routing requirements, that means how your URL paths will map to your views. For example you could build this entire thing with one route (e.g. /users) and use components to swap out different UI based on clicks. However that is generally considered bad UX and could also doesnât behave as naturally to the browser or to screen readers. It also means you have to either fetch all the data upfront or fetch it in components (which has some complexity as well). So in general itâs best to use actual routes for the list and detail pages. If you have routes then refresh or âdeep linkingâ works great, but if not then the UI is reset each time.
You can weigh the same decisions at the next level down too, the tabs. They could all just be components, or you could use one route per tab. Since thatâs generally the friendliest design, and we are discussing routing here, letâs imagine that the requirements are that we define the following URL mappings:
/users // maps to users list
/users/:id // maps to user detail (with "general" tab)
/users/:id/contact // maps to user detail (with "contact" tab)
/users/:id/permissions // maps to user detail (with "permissions" tab)
Solutions
So now that we have url => UI mapping requirements can take a look at the various approaches we have for building it.
Approach 1: one route, many components
As we mentioned in the requirements this isnât actually a viable option for us, and itâs rarely (if ever?) a good UX, but itâs technically an option so I might as well mention it. You could define one route like this:
// app/router.js
...
this.route('users')
which renders your list, and then if you click on a user it just renders a giant component with all the detail tabs.
{{! app/templates/users.hbs }}
<UsersList @users={{this.model.users}} @onClick={{this.setUser}} />
{{#if this.user}}
<UserDetail @user={{this.user}} />
{{/if}}
Pros:
- Simple route structure
- Common UI can be easily abstracted
Cons
- Does not meet our routing requirements
- Bad UX (detail/tabs lost on refresh)
- No deep linking
- Harder to make it accessible (links arenât really links so nav isnât really nav)
- Any additional data fetching or business logic must happen in the components
- Potentially complex component hierarchy
Approach 2: two (flat) routes, component tabs
This approach means simply defining another route for the detail page, and not nesting it, and not defining routes for the tabs
// app/router.js
...
this.route('users');
this.route('user', { path: '/users/:id' });
This means there are only 2 routes/templates.
{{! app/templates/users.hbs }}
<h1>Users</h1>
<UsersList @users={{this.model.users}} />
Letâs say the user list is just a component that renders the list of users given to it and has a link to the âuserâ page with that user id if clicked.
{{! app/components/users-list.hbs }}
<ul>
{{#each @users as |user|}}
<li>
<LinkTo @route="user" @model={{user.id}}>{{user.fullName}}</LinkTo>
</li>
{{/each}}
</ul>
So the detail page is just a component that renders all the detail tabs
{{! app/templates/users.hbs }}
<UserDetail @user={{this.user}} />
And the detail component could look something like this:
{{! app/components/user-detail.hbs}}
<h1>{{this.model.fullName}}</h1>
<div>
<button disabled={{eq this.tab "general"}} {{on "click" (fn this.setTab "general"}}>General</button>
<button disabled={{eq this.tab "contact"}} {{on "click" (fn this.setTab "contact"}}>Contact</button>
<button disabled={{eq this.tab "permissions"}} {{on "click" (fn this.setTab "permissions"}}>Permissions</button>
</div>
{{if (eq this.tab "contact")}}
<UserContact @user={{this.model}} />
{{else if (eq this.tab "permissions")}}
<UserPermissions @user={{this.model}} />
{{else}}
<UserGeneral @user={{this.model}} />
{{/if}}
This approach doesnât fulfill our requirements though, because we canât deep link into the tabs. One solution would be adapting it to use a query param for the tabs e.g. users/:id?tab=permissions
which would allow deep links and refreshes, but would still not fit our requirements exactly.
Pros:
- Simple route structure
- Supports loading extra data for detail page, deep link/refresh on detail page
Cons
- Does not meet our routing requirements
- Bad UX (tabs lost on refresh unless you use query params)
- No deep linking into tabs (unless you use query params)
- Harder to make it accessible (tabs arenât really links so nav isnât really nav, unless you use query params)
- Any additional data fetching or business logic for each tab must happen in the components
- Any UI that is common between the /users and /users/:id pages must be duplicated
- Still complex component hierarchy
Approach 3: two nested routes, component tabs
This approach is similar to the above Approach 2, however instead of a flat router we nest the detail under the parent:
// app/router.js
...
this.route('users', function() {
this.route('index') // NOTE: this is implicitly created even if you don't specify it
this.route('user', { path: '/:id' });
});
This actually results in three routes/templates (since the index is there whether you explicitly define it or not).
Then youâd rename the following files from Approach 2:
app/templates/users.hbs -> app/templates/users/index.hbs
app/templates/user.hbs -> app/templates/users/user.hbs
The user list doesnât really change in this case (although youâd make the links point to âusers.userâ instead of âuserâ). The user detail also basically stays the same.
This solution still doesnât meet our requirements and is mostly the same as Approach 2 with one difference (route names, e.g. âuserâ â âusers.userâ) and one key benefit: if there is any UI (headings, nav, etc) that is common between the âusers/indexâ and âusers/userâ templates, it can be abstracted into the âusers.hbsâ template.
In this case thereâs not really any data fetching that is shared between the users/index and users/user routes but if there was you could hoist it up into the users route.
Pros:
- Supports loading extra data for detail page, deep link/refresh on detail page
- Supports abstracting common UI between index/detail page
- Does not duplicate UI or data fetching for the detail page
Cons
- Does not meet our routing requirements
- Bad UX (tabs lost on refresh unless you use query params)
- No deep linking into tabs (unless you use query params)
- Harder to make it accessible (tabs arenât really links so nav isnât really nav, unless you use query params)
- Any additional data fetching or business logic for each tab must happen in the components
Approach 4: completely flat routes for each tab
This approach would actually meet our requirements. We could define one route for each detail tab, but avoid nesting the routes.
The router could look like this:
// app/router.js
...
this.route('users');
this.route('user-general', { path: '/users/:id' });
this.route('user-contact', { path: '/users/:id/contact' });
this.route('user-permissions', { path: '/users/:id/permissions' });
The user list doesnât really change in this case (although youâd make the links point to âuser-generalâ instead of âuserâ).
The user detail, however, is now split into multiple routes and templates:
{{! app/templates/user-general.hbs}}
<h1>{{this.model.fullName}}</h1>
<div>
<LinkTo @route="user-general" @model={{this.model}}>General</LinkTo>
<LinkTo @route="user-contact" @model={{this.model}}>Contact</LinkTo>
<LinkTo @route="user-permissions" @model={{this.model}}>Permissions</LinkTo>
</div>
<UserGeneral @user={{this.model}} />
{{! app/templates/user-contact.hbs}}
<h1>{{this.model.fullName}}</h1>
<div>
<LinkTo @route="user-general" @model={{this.model}}>General</LinkTo>
<LinkTo @route="user-contact" @model={{this.model}}>Contact</LinkTo>
<LinkTo @route="user-permissions" @model={{this.model}}>Permissions</LinkTo>
</div>
<UserContact @user={{this.model}} />
{{! app/templates/user-permissions.hbs}}
<h1>{{this.model.fullName}}</h1>
<div>
<LinkTo @route="user-general" @model={{this.model}}>General</LinkTo>
<LinkTo @route="user-contact" @model={{this.model}}>Contact</LinkTo>
<LinkTo @route="user-permissions" @model={{this.model}}>Permissions</LinkTo>
</div>
<UserPermissions @user={{this.model}} />
One thing youâll notice right away is that weâre duplicating A LOT of UI. And this is only a trivial example with a simple heading and tabbed nav. Imagine that you have a lot of page boilerplate to copy too. Gross! Another thing which is more subtle but possibly even worse is that you have to copy all the data fetching code across the tab routes. You need to make sure the user is loaded in each of the tab routes which means duplicating that in each route (assuming youâre loading the model in the routes).
So while this finally meets our routing requirements, it has some distinct downsides. Flatter isnât always better.
Pros:
- Meets our routing requirements
- Simple router structure (flat)
- Supports loading extra data for detail page
- Supports loading extra data for each tab
- Supports deep linking and refreshing on each tab
- Supports ârealâ navigation for better accessibility and UX
Cons
- Requires duplicating common UI between index/detail page
- Requires duplicating common UI between each tab of the detail page
- Requires duplicating the data gathering bits for each tab route
Approach 5: partially nested, flat detail routes
This approach is like a hybrid of 3 and 4, where we have separate routes for each tab as in Approach 4, but we nest them under âusersâ as in Approach 3.
The router could look like this:
// app/router.js
...
this.route('users', function() {
this.route('index'); // remember this one is implicit
this.route('general', { path: '/:id' });
this.route('contact', { path: '/:id/contact' });
this.route('permissions', { path: '/:id/permissions' });
});
The code is pretty much the same other than link paths, but youâd need to rename files like so:
app/templates/users.hbs -> app/templates/users/index.hbs
app/templates/user-general.hbs -> app/templates/users/general.hbs
app/templates/user-contact.hbs -> app/templates/users/contact.hbs
app/templates/user-permissions.hbs -> app/templates/users/permissions.hbs
So what does this get us? Not much, but it does give us the marginal benefit of not having to duplicate common UI between the index and detail page.
Pros:
- Meets our routing requirements
- Supports loading extra data for detail page
- Supports loading extra data for each tab
- Supports deep linking and refreshing on each tab
- Supports ârealâ navigation for better accessibility and UX
- Supports abstracting common UI between index/detail page (âusers.hbsâ template)
Cons
- Requires duplicating common UI between each tab of the detail page
- Requires duplicating the data gathering bits for each tab route
Approach 6: fully nested routes
This approach is where it all comes together (hopefully). We could do multiple levels of nesting, so the router looks like this:
// app/router.js
...
this.route('users', function() {
this.route('index'); // remember this one is implicit
this.route('user', function() {
this.route('index', { path: '/:id' });
this.route('contact', { path: '/:id/contact' });
this.route('permissions', { path: '/:id/permissions' });
});
});
If weâre working off approach 5 youâd need to rename files like so:
app/templates/users/general.hbs -> app/templates/users/user/index.hbs
app/templates/users/contact.hbs -> app/templates/users/user/contact.hbs
app/templates/users/permissions.hbs -> app/templates/users/user/permissions.hbs
BUT then you can move all the shared bits into /users/user.hbs and avoid all the UI duplication:
{{! app/templates/users/user.hbs }}
<h1>{{this.model.fullName}}</h1>
<div>
<LinkTo @route="user-general" @model={{this.model}}>General</LinkTo>
<LinkTo @route="user-contact" @model={{this.model}}>Contact</LinkTo>
<LinkTo @route="user-permissions" @model={{this.model}}>Permissions</LinkTo>
</div>
{{outlet}}
{{! app/templates/users/user/general.hbs}}
<UserGeneral @user={{this.model}} />
{{! app/templates/users/user/contact.hbs}}
<UserContact @user={{this.model}} />
{{! app/templates/users/user/permissions.hbs}}
<UserPermissions @user={{this.model}} />
And this also allows you to do the data fetching for the detail page only once (in app/routes/users/user.js). It supports all our routing requirements, and it also gives you a lot of flexibility so each route can load extra data or define query params or whatever. Now our pros/cons list looks pretty great.
Pros:
- Meets our routing requirements
- Supports loading extra data for detail page
- Supports loading extra data for each tab
- Supports deep linking and refreshing on each tab
- Supports ârealâ navigation for better accessibility and UX
- Supports abstracting common UI between index/detail page (âusers.hbsâ template)
- Supports abstracting common UI between all detail tabs (âusers/user.hbsâ template)
- Supports abstracting the data fetching for the detail page and only doing it once
Cons
- Somewhat complex router
Conclusions
Hopefully this illustrates why nesting routes can be beneficial. In broad strokes it lets you abstract/consolidate your data fetching and your UI rendering to match your page structure/function. Tabbed nav is just one common example of where nested routes might come in handy and youâll need to abstract that common UI.
This was a simple example and didnât touch on loading and error substates, etc but that can also play a factor in your router design.
We can generalize a few ideas here which might be helpful to summarize:
- the router maps browser URLs to both data fetching and UI rendering. Or put differently, the router maps URLs to a âpageâ (which is made up of the data it needs to fetch and the UI it needs to render).
- nested routes mean nested UI and nested route invocation: if you nest your routes it means that the UI is always nested, and it means that route hooks will fire in a descending hierarchy, which can help mitigate data dependency concerns, but can also block rendering of child routes until the parent has resolved
- nested is probably more flexible: (IF nested routes make sense given the above two ideas). For simple cases a flat structure might work well and be a little easier to get started with, however as your application grows and changes you may need to refactor. Starting with nested routes can keep your routing design open ended and flexible and give you a lot of room to run without major refactors.
We could really dive into a lot more detail here but hopefully thatâs enough to answer your question and give some helpful rules of thumb for when to nest routes and when to not nest them.
Also FWIW the Ember router lets you do some crazier stuff because thereâs the âroute mapâ as an extra layer of abstraction. Most component frameworks use file-based routers which means that in a system like that the Approach 2 and Approach 4 solutions probably wouldnât even be possible. Does that mean those approaches are wrong? Not necessarily⌠but I personally like to keep a strong correlation between the router file system organization and the actual route map.