I am little combersome about main route template along with index.hbs

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

1 Like

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.

1 Like

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.