Linking Between Routes

Its important to be able to switch between routes and link to different parts of your application. We can do this declaratively in templates using the <LinkTo> component.

The <LinkTo /> Component

You create a link to a route using the <LinkTo /> component.

Router.map(function() {
  this.route('photos', function(){
    this.route('edit', { path: '/:photo_id' });
  });
});
<ul>
  {{#each this.photos as |p|}}
    <li>
      <LinkTo @route="photos.edit" @model={{p}}>{{p.title}}</LinkTo>
    </li>
  {{/each}}
</ul>

The @route argument is the name of the route to link to, and the @model argument is a model object to fill in the dynamic segment for the route.

For example, if this.photos is a list of three photos, the rendered HTML would look something like this:

<ul>
  <li>
    <a href="/photos/1">Happy Kittens</a>
  </li>
  <li>
    <a href="/photos/2">Puppy Running</a>
  </li>
  <li>
    <a href="/photos/3">Mountain Landscape</a>
  </li>
</ul>

By default, Ember.js will replace each dynamic segment in the URL with the model object's id property. In the example above, the @model argument is the photo objects, and their id properties are used to fill in the dynamic segment in the URL; in this case, either 1, 2, or 3. This behavior can be customized within PhotoEditRoute's serialize hook.

Alternatively, you can explicitly provide a serialized id, in place of passing a model object:

<LinkTo @route="photos.edit" @model="1">First Photo Ever</LinkTo>

In this case, the provided id will be used to populate the URL's dynamic segment directly, bypassing the serialize hook entirely:

<a href="/photos/1">First Photo Ever</a>

When the user clicks on the link, Ember will run the PhotoEditRoute's model hook with params.photo_id = 1. On the other hand, if a model object was passed instead of the id, the model hook will not run.

Active CSS Class

When the generated link matches the current URL, then the generated link tag will be given the active CSS class. For example, if you were at the URL /photos/2, the first example above would render as:

<ul>
  <li>
    <a href="/photos/1">Happy Kittens</a>
  </li>
  <li>
    <a href="/photos/2" class="active">Puppy Running</a>
  </li>
  <li>
    <a href="/photos/3">Mountain Landscape</a>
  </li>
</ul>

Multiple Dynamic Segments

Sometimes, you may need to generate links for nested routes which can have multiple dynamic segments. For example, consider the following route definitions:

Router.map(function() {
  this.route('photos', function(){
    this.route('photo', { path: '/:photo_id' }, function(){
      this.route('comments');
      this.route('comment', { path: '/comments/:comment_id' });
    });
  });
});

Here, the photos.photo.comment route have two dynamic segments: :photo_id and :comment_id.

When passing a @model object to the <LinkTo /> component, that single model object will be used to populate the innermost dynamic segment. In this case, that would be :comment_id. The :photo_id will be inferred from the current URL.

For example, if we are currently on /photos/2, then the following template:

{{#each this.photo.comments as |comment|}}
  <LinkTo @route="photos.photo.comment" @model={{comment}}>
    {{excerpt comment.body}}...
  </LinkTo>
{{/each}}

...will render something like this:

<a href="/photos/2/comment/37">
  Aww this is...
</a>
<a href="/photos/2/comment/44">
  Great puppy...
</a>
<a href="/photos/2/comment/45">
  5/5 would pet...
</a>

Note that while :comment_id is populated with each comment's id (based on the @model argument), the :photo_id segment is automatically assumed to be the same as the corresponding segment in current URL, i.e. 2.

Ember is only able to infer the dynamic segments because the photo route is currently active. If we were to invoke the <LinkTo /> component for the same photos.photo.comment route, but from the photos route's template, it will result in an error, as we did not pass enough model objects to populate all the dynamic segments needed to generate the URL.

To solve this problem, or maybe to cross-link comments from photos other than the currently active one, you can pass an array of model objects using the @models argument and the {{array}} helper:

<h1>Latest Comments</h1>

<ul>
  {{#each this.latestComments as |comment|}}
    <li>
      <LinkTo @route="photos.photo.comment" @models={{array comment.photo comment}}>
        {{excerpt comment.body}}...
      </LinkTo>
    </li>
  {{/each}}
</ul>

Here, we are passing an array of model objects (the photo, then the comment), which is exactly what is needed to populate all the dynamic segments.

The @model argument is merely a special case for the more general @models argument. Therefore, it is an error to pass both arguments at the same time.

Query Params

The @query argument, along with the {{hash}} helper, can be used to set query params on a link:

// Explicitly set target query params
<LinkTo @route="posts" @query={{hash direction="asc"}}>Sort</LinkTo>

// Binding is also supported
<LinkTo @route="posts" @query={{hash direction=this.otherDirection}}>Sort</LinkTo>

For more information on how to use query parameters see the query parameters section in Routing.

HTML Attributes

When generating a link, you may want to customize its HTML attributes. For example, it is quite common to want to add additional CSS classes to the generated link tag, or specifying the appropriate ARIA attributes. You can simply pass them along with the invocation:

<LinkTo @route="photos" class="btn btn-primary" role="button" aria-pressed="false">
  Discard Changes
</LinkTo>

CSS classes passed this way will be in addition to the standard ember-view and possibly active classes.

Note that the <LinkTo /> component uses the element's id HTML attribute internally for event dispatching purposes. For that reason, if you would like to customize its HTML id, you must pass it as the @id argument instead. Overriding the components id attribute directly will stop the link from functioning correctly.

Replacing history entries

The default behavior for the <LinkTo /> component is to add entries to the browser's history when transitioning between routes. However, to replace the current entry in the browser's history instead, you can use the @replace option:

<LinkTo @route="photo.comment" @model={{this.topComment}} @replace={{true}}>
  Top comment for the current photo
</Link>

© 2020 Yehuda Katz, Tom Dale and Ember.js contributors
Licensed under the MIT License.
https://guides.emberjs.com/v3.25.0/routing/linking-between-routes