Routing

Introduction

Routing

As users interact with your application, it moves through many different states. Ember.js gives you helpful tools for managing that state in a way that scales with your application.

To understand why this is important, imagine we are writing a web app for managing a blog. At any given time, we should be able to answer questions like: Is the user currently logged in? Are they an admin user? What post are they looking at? Is the settings screen open? Are they editing the current post?

In Ember.js, each of the possible states in your application is represented by a URL. Because all of the questions we asked above— Are we logged in? What post are we looking at? —are encapsulated by route handlers for the URLs, answering them is both simple and accurate.

At any given time, your application has one or more active route handlers. The active handlers can change for one of two reasons:

  1. The user interacted with a view, which generated an event that caused the URL to change.
  2. The user changed the URL manually (e.g., via the back button), or the page was loaded for the first time.

When the current URL changes, the newly active route handlers may do one or more of the following:

  1. Conditionally redirect to a new URL.
  2. Update a controller so that it represents a particular model.
  3. Change the template on screen, or place a new template into an existing outlet.
Logging Route Changes

As your application increases in complexity, it can be helpful to see exactly what is going on with the router. To have Ember write out transition events to the log, simply modify your Ember.Application:

1 App = Ember.Application.create({
2   LOG_TRANSITIONS: true
3 });
Specifying a Root URL

If your Ember application is one of multiple web applications served from the same domain, it may be necessary to indicate to the router what the root URL for your Ember application is. By default, Ember will assume it is served from the root of your domain.

If for example, you wanted to serve your blogging application from www.emberjs.com/blog/, it would be necessary to specify a root URL of /blog/.

This can be achieved by setting the rootURL on the router:

1 App.Router.reopen({
2   rootURL: '/blog/'
3 });

Defining Your Routes

When your application starts, the router is responsible for displaying templates, loading data, and otherwise setting up application state. It does so by matching the current URL to the routes that you’ve defined.

The map method of your Ember application’s router can be invoked to define URL mappings. When calling map, you should pass a function that will be invoked with the value this set to an object which you can use to create routes and resources.

1 App.Router.map(function() {
2   this.route("about", { path: "/about" });
3   this.route("favorites", { path: "/favs" });
4 });

Now, when the user visits /about, Ember.js will render the about template. Visiting /favs will render the favorites template.

Note that you can leave off the path if it is the same as the route name. In this case, the following is equivalent to the above example:

1 App.Router.map(function() {
2   this.route("about");
3   this.route("favorites", { path: "/favs" });
4 });

Inside your templates, you can use {{link-to}} to navigate between routes, using the name that you provided to the route method (or, in the case of /, the name index).

1 {{#link-to 'index'}}<img class="logo">{{/link-to}}
2 
3 <nav>
4   {{#link-to 'about'}}About{{/link-to}}
5   {{#link-to 'favorites'}}Favorites{{/link-to}}
6 </nav>

The {{link-to}} helper will also add an active class to the link that points to the currently active route.

You can customize the behavior of a route by creating an Ember.Route subclass. For example, to customize what happens when your user visits /, create an App.IndexRoute:

1 App.IndexRoute = Ember.Route.extend({
2   setupController: function(controller) {
3     // Set the IndexController's `title`
4     controller.set('title', "My App");
5   }
6 });

The IndexController is the starting context for the index template. Now that you’ve set title, you can use it in the template:

1 <!-- get the title from the IndexController -->
2 <h1>{{title}}</h1>

(If you don’t explicitly define an App.IndexController, Ember.js will automatically generate one for you.)

Ember.js automatically figures out the names of the routes and controllers based on the name you pass to this.route.

Resources

You can define groups of routes that work with a resource:

1 App.Router.map(function() {
2   this.resource('posts', { path: '/posts' }, function() {
3     this.route('new');
4   });
5 });

As with this.route, you can leave off the path if it’s the same as the name of the route, so the following router is equivalent:

1 App.Router.map(function() {
2   this.resource('posts', function() {
3     this.route('new');
4   });
5 });

This router creates three routes:

<small><sup>1</sup> Transitioning to posts or creating a link to posts is equivalent to transitioning to posts.index or linking to posts.index</small>

NOTE: If you define a resource using this.resource and do not supply a function, then the implicit resource.index route is not created. In that case, /resource will only use the ResourceRoute, ResourceController, and resource template.

Routes nested under a resource take the name of the resource plus their name as their route name. If you want to transition to a route (either via transitionTo or {{#link-to}}), make sure to use the full route name (posts.new, not new).

Visiting / renders the index template, as you would expect.

Visiting /posts is slightly different. It will first render the posts template. Then, it will render the posts/index template into the posts template’s outlet.

Finally, visiting /posts/new will first render the posts template, then render the posts/new template into its outlet.

NOTE: You should use this.resource for URLs that represent a noun, and this.route for URLs that represent adjectives or verbs modifying those nouns. For example, in the code sample above, when specifying URLs for posts (a noun), the route was defined with this.resource('posts'). However, when defining the new action (a verb), the route was defined with this.route('new').

Dynamic Segments

One of the responsibilities of a resource’s route handler is to convert a URL into a model.

For example, if we have the resource this.resource('posts');, our route handler might look like this:

1 App.PostsRoute = Ember.Route.extend({
2   model: function() {
3     return this.store.find('posts');
4   }
5 });

The posts template will then receive a list of all available posts as its context.

Because /posts represents a fixed model, we don’t need any additional information to know what to retrieve. However, if we want a route to represent a single post, we would not want to have to hardcode every possible post into the router.

Enter dynamic segments.

A dynamic segment is a portion of a URL that starts with a : and is followed by an identifier.

 1 App.Router.map(function() {
 2   this.resource('posts');
 3   this.resource('post', { path: '/post/:post_id' });
 4 });
 5 
 6 App.PostRoute = Ember.Route.extend({
 7   model: function(params) {
 8     return this.store.find('post', params.post_id);
 9   }
10 });

Because this pattern is so common, the above model hook is the default behavior.

For example, if the dynamic segment is :post_id, Ember.js is smart enough to know that it should use the model App.Post (with the ID provided in the URL). Specifically, unless you override model, the route will return this.store.find('post', params.post_id) automatically.

Not coincidentally, this is exactly what Ember Data expects. So if you use the Ember router with Ember Data, your dynamic segments will work as expected out of the box.

If your model does not use the id property in the URL, you should define a serialize method on your route:

 1 App.Router.map(function() {
 2   this.resource('post', {path: '/posts/:post_slug'});
 3 });
 4 
 5 App.PostRoute = Ember.Route.extend({
 6   model: function(params) {
 7     // the server returns `{ slug: 'foo-post' }`
 8     return jQuery.getJSON("/posts/" + params.post_slug);
 9   },
10 
11   serialize: function(model) {
12     // this will make the URL `/posts/foo-post`
13     return { post_slug: model.get('slug') };
14   }
15 });

The default serialize method inserts the model’s id into the route’s dynamic segment (in this case, :post_id).

Nested Resources

You can nest both routes and resources:

1 App.Router.map(function() {
2   this.resource('post', { path: '/post/:post_id' }, function() {
3     this.route('edit');
4     this.resource('comments', function() {
5       this.route('new');
6     });
7   });
8 });

This router creates five routes:

<small><sup>2</sup> :post_id is the post’s id. For a post with id = 1, the route will be: /post/1</small>

The comments template will be rendered in the post outlet. All templates under comments (comments/index and comments/new) will be rendered in the comments outlet.

The route, controller, and view class names for the comments resource are not prefixed with Post. Resources always reset the namespace, ensuring that the classes can be re-used between multiple parent resources and that class names don’t get longer the deeper nested the resources are.

You are also able to create deeply nested resources in order to preserve the namespace on your routes:

1 App.Router.map(function() {
2   this.resource('foo', function() {
3     this.resource('foo.bar', { path: '/bar' }, function() {
4       this.route('baz'); // This will be foo.bar.baz
5     });
6   });
7 });

This router creates the following routes:

Initial routes

A few routes are immediately available within your application:

  • App.ApplicationRoute is entered when your app first boots up. It renders the application template.
  • App.IndexRoute is the default route, and will render the index template when the user visits / (unless / has been overridden by your own custom route).

Remember, these routes are part of every application, so you don’t need to specify them in App.Router.map.

Wildcard / globbing routes

You can define wildcard routes that will match multiple routes. This could be used, for example, if you’d like a catchall route which is useful when the user enters an incorrect URL not managed by your app.

1 App.Router.map(function() {
2   this.route('catchall', {path: '/*wildcard'});
3 });

Like all routes with a dynamic segment, you must provide a context when using a {{link-to}} or transitionTo to programatically enter this route.

1 App.ApplicationRoute = Ember.Route.extend({
2   actions: {
3     error: function () {
4       this.transitionTo('catchall', "application-error");
5     }
6   }
7 });

With this code, if an error bubbles up to the Application route, your application will enter the catchall route and display /application-error in the URL.

Generated Objects

As explained in the routing guide, whenever you define a new route, Ember.js attempts to find corresponding Route, Controller, View, and Template classes named according to naming conventions. If an implementation of any of these objects is not found, appropriate objects will be generated in memory for you.

Generated routes

Given you have the following route:

1 App.Router.map(function() {
2   this.resource('posts');
3 });

When you navigate to /posts, Ember.js looks for App.PostsRoute. If it doesn’t find it, it will automatically generate an App.PostsRoute for you.

Custom Generated Routes

You can have all your generated routes extend a custom route. If you define App.Route, all generated routes will be instances of that route.

Generated Controllers

If you navigate to route posts, Ember.js looks for a controller called App.PostsController. If you did not define it, one will be generated for you.

Ember.js can generate three types of controllers: Ember.ObjectController, Ember.ArrayController, and Ember.Controller.

The type of controller Ember.js chooses to generate for you depends on your route’s model hook:

  • If it returns an object (such as a single record), an ObjectController will be generated.
  • If it returns an array, an ArrayController will be generated.
  • If it does not return anything, an instance of Ember.Controller will be generated.
Custom Generated Controllers

If you want to customize generated controllers, you can define your own App.Controller, App.ObjectController and App.ArrayController. Generated controllers will extend one of these three (depending on the conditions above).

Generated Views and Templates

A route also expects a view and a template. If you don’t define a view, a view will be generated for you.

A generated template is empty. If it’s a resource template, the template will simply act as an outlet so that nested routes can be seamlessly inserted. It is equivalent to:

1 {{outlet}}

Specifying A Routes Model

Templates in your application are backed by models. But how do templates know which model they should display?

For example, if you have a photos template, how does it know which model to render?

This is one of the jobs of an Ember.Route. You can tell a template which model it should render by defining a route with the same name as the template, and implementing its model hook.

For example, to provide some model data to the photos template, we would define an App.PhotosRoute object:

 1 App.PhotosRoute = Ember.Route.extend({
 2   model: function() {
 3     return [{
 4       title: "Tomster",
 5       url: "http://emberjs.com/images/about/ember-productivity-sm.png"
 6     }, {
 7       title: "Eiffel Tower",
 8       url: "http://emberjs.com/images/about/ember-structure-sm.png"
 9     }];
10   }
11 });

JS Bin

Asynchronously Loading Models

In the above example, the model data was returned synchronously from the model hook. This means that the data was available immediately and your application did not need to wait for it to load, in this case because we immediately returned an array of hardcoded data.

Of course, this is not always realistic. Usually, the data will not be available synchronously, but instead must be loaded asynchronously over the network. For example, we may want to retrieve the list of photos from a JSON API available on our server.

In cases where data is available asynchronously, you can just return a promise from the model hook, and Ember will wait until that promise is resolved before rendering the template.

If you’re unfamiliar with promises, the basic idea is that they are objects that represent eventual values. For example, if you use jQuery’s getJSON() method, it will return a promise for the JSON that is eventually returned over the network. Ember uses this promise object to know when it has enough data to continue rendering.

For more about promises, see A Word on Promises in the Asynchronous Routing guide.

Let’s look at an example in action. Here’s a route that loads the most recent pull requests sent to Ember.js on GitHub:

1 App.PullRequestsRoute = Ember.Route.extend({
2   model: function() {
3     return Ember.$.getJSON('https://api.github.com/repos/emberjs/ember.js/pulls'\
4 );
5   }
6 });

While this example looks like it’s synchronous, making it easy to read and reason about, it’s actually completely asynchronous. That’s because jQuery’s getJSON() method returns a promise. Ember will detect the fact that you’ve returned a promise from the model hook, and wait until that promise resolves to render the pullRequests template.

(For more information on jQuery’s XHR functionality, see jQuery.ajax in the jQuery documentation.)

Because Ember supports promises, it can work with any persistence library that uses them as part of its public API. You can also use many of the conveniences built in to promises to make your code even nicer.

For example, imagine if we wanted to modify the above example so that the template only displayed the three most recent pull requests. We can rely on promise chaining to modify the data returned from the JSON request before it gets passed to the template:

1 App.PullRequestsRoute = Ember.Route.extend({
2   model: function() {
3     var url = 'https://api.github.com/repos/emberjs/ember.js/pulls';
4     return Ember.$.getJSON(url).then(function(data) {
5       return data.splice(0, 3);
6     });
7   }
8 });

Setting Up Controllers with the Model

So what actually happens with the value you return from the model hook?

By default, the value returned from your model hook will be assigned to the model property of the associated controller. For example, if your App.PostsRoute returns an object from its model hook, that object will be set as the model property of the App.PostsController.

(This, under the hood, is how templates know which model to render: they look at their associated controller’s model property. For example, the photos template will render whatever the App.PhotosController’s model property is set to.)

See the Setting Up a Controller guide to learn how to change this default behavior. Note that if you override the default behavior and do not set the model property on a controller, your template will not have any data to render!

Dynamic Models

Some routes always display the same model. For example, the /photos route will always display the same list of photos available in the application. If your user leaves this route and comes back later, the model does not change.

However, you will often have a route whose model will change depending on user interaction. For example, imagine a photo viewer app. The /photos route will render the photos template with the list of photos as the model, which never changes. But when the user clicks on a particular photo, we want to display that model with the photo template. If the user goes back and clicks on a different photo, we want to display the photo template again, this time with a different model.

In cases like this, it’s important that we include some information in the URL about not only which template to display, but also which model.

In Ember, this is accomplished by defining routes with dynamic segments.

A dynamic segment is a part of the URL that is filled in by the current model’s ID. Dynamic segments always start with a colon (:). Our photo example might have its photo route defined like this:

1 App.Router.map(function() {
2   this.resource('photo', { path: '/photos/:photo_id' });
3 });

In this example, the photo route has a dynamic segment :photo_id. When the user goes to the photo route to display a particular photo model (usually via the {{link-to}} helper), that model’s ID will be placed into the URL automatically.

See Links for more information about linking to a route with a model using the {{link-to}} helper.

For example, if you transitioned to the photo route with a model whose id property was 47, the URL in the user’s browser would be updated to:

1 /photos/47

What happens if the user visits your application directly with a URL that contains a dynamic segment? For example, they might reload the page, or send the link to a friend, who clicks on it. At that point, because we are starting the application up from scratch, the actual JavaScript model object to display has been lost; all we have is the ID from the URL.

Luckily, Ember will extract any dynamic segments from the URL for you and pass them as a hash to the model hook as the first argument:

1 App.Router.map(function() {
2   this.resource('photo', { path: '/photos/:photo_id' });
3 });
4 
5 App.PhotoRoute = Ember.Route.extend({
6   model: function(params) {
7     return Ember.$.getJSON('/photos/'+params.photo_id);
8   }
9 });

In the model hook for routes with dynamic segments, it’s your job to turn the ID (something like 47 or post-slug) into a model that can be rendered by the route’s template. In the above example, we use the photo’s ID (params.photo_id) to construct a URL for the JSON representation of that photo. Once we have the URL, we use jQuery to return a promise for the JSON model data.

Note: A route with a dynamic segment will only have its model hook called when it is entered via the URL. If the route is entered through a transition (e.g. when using the link-to Handlebars helper), then a model context is already provided and the hook is not executed. Routes without dynamic segments will always execute the model hook.

Refreshing your model

If your data represented by your model is being updated frequently, you may want to refresh it periodically:

JS Bin

The controller can send an action to the Route; in this example above, the IndexController exposes an action getLatest which sends the route an action called invalidateModel. Calling the route’s refresh method will force Ember to execute the model hook again.

Ember Data

Many Ember developers use a model library to make finding and saving records easier than manually managing Ajax calls. In particular, using a model library allows you to cache records that have been loaded, significantly improving the performance of your application.

One popular model library built for Ember is Ember Data. To learn more about using Ember Data to manage your models, see the Models guide.

Setting Up A Controller

Changing the URL may also change which template is displayed on screen. Templates, however, are usually only useful if they have some source of information to display.

In Ember.js, a template retrieves information to display from a controller.

Two built-in controllers—Ember.ObjectController and Ember.ArrayController—make it easy for a controller to present a model’s properties to a template, along with any additional display-specific properties.

To tell one of these controllers which model to present, set its model property in the route handler’s setupController hook.

 1 App.Router.map(function() {
 2   this.resource('post', { path: '/posts/:post_id' });
 3 });
 4 
 5 App.PostRoute = Ember.Route.extend({
 6   // The code below is the default behavior, so if this is all you
 7   // need, you do not need to provide a setupController implementation
 8   // at all.
 9   setupController: function(controller, model) {
10     controller.set('model', model);
11   }
12 });

The setupController hook receives the route handler’s associated controller as its first argument. In this case, the PostRoute’s setupController receives the application’s instance of App.PostController.

To specify a controller other than the default, set the route’s controllerName property:

1 App.SpecialPostRoute = Ember.Route.extend({
2   controllerName: 'post'
3 });

As a second argument, it receives the route handler’s model. For more information, see Specifying a Route’s Model.

The default setupController hook sets the model property of the associated controller to the route handler’s model.

If you want to configure a controller other than the controller associated with the route handler, use the controllerFor method:

1 App.PostRoute = Ember.Route.extend({
2   setupController: function(controller, model) {
3     this.controllerFor('topPost').set('model', model);
4   }
5 });

Rendering A Template

One of the most important jobs of a route handler is rendering the appropriate template to the screen.

By default, a route handler will render the template into the closest parent with a template.

1 App.Router.map(function() {
2   this.resource('posts');
3 });
4 
5 App.PostsRoute = Ember.Route.extend();

If you want to render a template other than the one associated with the route handler, implement the renderTemplate hook:

1 App.PostsRoute = Ember.Route.extend({
2   renderTemplate: function() {
3     this.render('favoritePost');
4   }
5 });

If you want to use a different controller than the route handler’s controller, pass the controller’s name in the controller option:

1 App.PostsRoute = Ember.Route.extend({
2   renderTemplate: function() {
3     this.render({ controller: 'favoritePost' });
4   }
5 });

Ember allows you to name your outlets. For instance, this code allows you to specify two outlets with distinct names:

1 <div class="toolbar">{{outlet toolbar}}</div>
2 <div class="sidebar">{{outlet sidebar}}</div>

So, if you want to render your posts into the sidebar outlet, use code like this:

1 App.PostsRoute = Ember.Route.extend({
2   renderTemplate: function() {
3     this.render({ outlet: 'sidebar' });
4   }
5 });

All of the options described above can be used together in whatever combination you’d like:

 1 App.PostsRoute = Ember.Route.extend({
 2   renderTemplate: function() {
 3     var controller = this.controllerFor('favoritePost');
 4 
 5     // Render the `favoritePost` template into
 6     // the outlet `posts`, and use the `favoritePost`
 7     // controller.
 8     this.render('favoritePost', {
 9       outlet: 'posts',
10       controller: controller
11     });
12   }
13 });

If you want to render two different templates into outlets of two different rendered templates of a route:

 1 App.PostRoute = App.Route.extend({
 2   renderTemplate: function() {
 3     this.render('favoritePost', {   // the template to render
 4       into: 'posts',                // the template to render into
 5       outlet: 'posts',              // the name of the outlet in that template
 6       controller: 'blogPost'        // the controller to use for the template
 7     });
 8     this.render('comments', {
 9       into: 'favoritePost',
10       outlet: 'comment',
11       controller: 'blogPost'
12     });
13   }
14 });

Redirecting

Transitioning and Redirecting

Calling transitionTo from a route or transitionToRoute from a controller will stop any transition currently in progress and start a new one, functioning as a redirect. transitionTo takes parameters and behaves exactly like the link-to helper:

  • If you transition into a route without dynamic segments that route’s model hook will always run.
  • If the new route has dynamic segments, you need to pass either a model or an identifier for each segment. Passing a model will skip that segment’s model hook. Passing an identifier will run the model hook and you’ll be able to access the identifier in the params. See Links for more detail.

Before the model is known

If you want to redirect from one route to another, you can do the transition in the beforeModel hook of your route handler.

1 App.Router.map(function() {
2   this.resource('posts');
3 });
4 
5 App.IndexRoute = Ember.Route.extend({
6   beforeModel: function() {
7     this.transitionTo('posts');
8   }
9 });

After the model is known

If you need some information about the current model in order to decide about the redirection, you should either use the afterModel or the redirect hook. They receive the resolved model as the first parameter and the transition as the second one, and thus function as aliases. (In fact, the default implementation of afterModel just calls redirect.)

 1 App.Router.map(function() {
 2   this.resource('posts');
 3   this.resource('post', { path: '/post/:post_id' });
 4 });
 5 
 6 App.PostsRoute = Ember.Route.extend({
 7   afterModel: function(posts, transition) {
 8     if (posts.get('length') === 1) {
 9       this.transitionTo('post', posts.get('firstObject'));
10     }
11   }
12 });

When transitioning to the PostsRoute if it turns out that there is only one post, the current transition will be aborted in favor of redirecting to the PostRoute with the single post object being its model.

Based on other application state

You can conditionally transition based on some other application state.

 1 App.Router.map(function() {
 2   this.resource('topCharts', function() {
 3     this.route('choose', { path: '/' });
 4     this.route('albums');
 5     this.route('songs');
 6     this.route('artists');
 7     this.route('playlists');
 8   });
 9 });
10 
11 App.TopChartsChooseRoute = Ember.Route.extend({
12   beforeModel: function() {
13     var lastFilter = this.controllerFor('application').get('lastFilter');
14     this.transitionTo('topCharts.' + (lastFilter || 'songs'));
15   }
16 });
17 
18 // Superclass to be used by all of the filter routes below
19 App.FilterRoute = Ember.Route.extend({
20   activate: function() {
21     var controller = this.controllerFor('application');
22     controller.set('lastFilter', this.templateName);
23   }
24 });
25 
26 App.TopChartsSongsRoute = App.FilterRoute.extend();
27 App.TopChartsAlbumsRoute = App.FilterRoute.extend();
28 App.TopChartsArtistsRoute = App.FilterRoute.extend();
29 App.TopChartsPlaylistsRoute = App.FilterRoute.extend();

In this example, navigating to the / URL immediately transitions into the last filter URL that the user was at. The first time, it transitions to the /songs URL.

Your route can also choose to transition only in some cases. If the beforeModel hook does not abort or transition to a new route, the remaining hooks (model, afterModel, setupController, renderTemplate) will execute as usual.

Specifying The URL Type

By default the Router uses the browser’s hash to load the starting state of your application and will keep it in sync as you move around. At present, this relies on a hashchange event existing in the browser.

Given the following router, entering /#/posts/new will take you to the posts.new route.

1 App.Router.map(function() {
2   this.resource('posts', function() {
3     this.route('new');
4   });
5 });

If you want /posts/new to work instead, you can tell the Router to use the browser’s history API.

Keep in mind that your server must serve the Ember app at all the routes defined here.

1 App.Router.reopen({
2   location: 'history'
3 });

Finally, if you don’t want the browser’s URL to interact with your application at all, you can disable the location API entirely. This is useful for testing, or when you need to manage state with your Router, but temporarily don’t want it to muck with the URL (for example when you embed your application in a larger page).

1 App.Router.reopen({
2   location: 'none'
3 });

Query Parameters

Query parameters are optional key-value pairs that appear to the right of the ? in a URL. For example, the following URL has two query params, sort and page, with respective values ASC and 2:

1 http://example.com/articles?sort=ASC&page=2

Query params allow for additional application state to be serialized into the URL that can’t otherwise fit into the path of the URL (i.e. everything to the left of the ?). Common use cases for query params include representing the current page, filter criteria, or sorting criteria.

Specifying Query Parameters

Query params can be declared on route-driven controllers, e.g. to configure query params that are active within the articles route, they must be declared on ArticlesController.

Let’s say we’d like to add a category query parameter that will filter out all the articles that haven’t been categorized as popular. To do this, we specify 'category' as one of ArticlesController’s queryParams:

1 App.ArticlesController = Ember.ArrayController.extend({
2   queryParams: ['category'],
3   category: null
4 });

This sets up a binding between the category query param in the URL, and the category property on ArticlesController. In other words, once the articles route has been entered, any changes to the category query param in the URL will update the category property on ArticlesController, and vice versa.

Now we just need to define a computed property of our category-filtered array that the articles template will render:

 1 App.ArticlesController = Ember.ArrayController.extend({
 2   queryParams: ['category'],
 3   category: null,
 4 
 5   filteredArticles: function() {
 6     var category = this.get('category');
 7     var articles = this.get('model');
 8 
 9     if (category) {
10       return articles.filterBy('category', category);
11     } else {
12       return articles;
13     }
14   }.property('category', 'model')
15 });

With this code, we have established the following behaviors:

  1. If the user navigates to /articles, category will be null, so the articles won’t be filtered.
  2. If the user navigates to /articles?category=recent, category will be set to "recent", so articles will be filtered.
  3. Once inside the articles route, any changes to the category property on ArticlesController will cause the URL to update the query param. By default, a query param property change won’t cause a full router transition (i.e. it won’t call model hooks and setupController, etc.); it will only update the URL.

The link-to helper supports specifying query params by way of the query-params subexpression helper.

1 // Explicitly set target query params
2 {{#link-to 'posts' (query-params direction="asc")}}Sort{{/link-to}}
3 
4 // Binding is also supported
5 {{#link-to 'posts' (query-params direction=otherDirection)}}Sort{{/link-to}}

In the above examples, direction is presumably a query param property on the PostsController, but it could also refer to a direction property on any of the controllers associated with the posts route hierarchy, matching the leaf-most controller with the supplied property name.

The link-to helper takes into account query parameters when determining its “active” state, and will set the class appropriately. The active state is determined by calculating whether the query params end up the same after clicking a link. You don’t have to supply all of the current, active query params for this to be true.

transitionTo

Route#transitionTo (and Controller#transitionToRoute) now accepts a final argument, which is an object with the key queryParams.

1 this.transitionTo('post', object, {queryParams: {showDetails: true}});
2 this.transitionTo('posts', {queryParams: {sort: 'title'}});
3 
4 // if you just want to transition the query parameters without changing the route
5 this.transitionTo({queryParams: {direction: 'asc'}});

You can also add query params to URL transitions:

1 this.transitionTo("/posts/1?sort=date&showDetails=true");

Opting into a full transition

Keep in mind that if the arguments provided to transitionTo or link-to only correspond to a change in query param values, and not a change in the route hierarchy, it is not considered a full transition, which means that hooks like model and setupController won’t fire by default, but rather only controller properties will be updated with new query param values, as will the URL.

But some query param changes necessitate loading data from the server, in which case it is desirable to opt into a full-on transition. To opt into a full transition when a controller query param property changes, you can use the optional queryParams configuration hash on the Route associated with that controller, and set that query param’s refreshModel config property to true:

 1 App.ArticlesRoute = Ember.Route.extend({
 2   queryParams: {
 3     category: {
 4       refreshModel: true
 5     }
 6   },
 7   model: function(params) {
 8     // This gets called upon entering 'articles' route
 9     // for the first time, and we opt into refiring it upon
10     // query param changes by setting `refreshModel:true` above.
11 
12     // params has format of { category: "someValueOrJustNull" },
13     // which we can just forward to the server.
14     return this.store.findQuery('articles', params);
15   }
16 });
17 
18 App.ArticlesController = Ember.ArrayController.extend({
19   queryParams: ['category'],
20   category: null
21 });

Update URL with replaceState instead

By default, Ember will use pushState to update the URL in the address bar in response to a controller query param property change, but if you would like to use replaceState instead (which prevents an additional item from being added to your browser’s history), you can specify this on the Route’s queryParams config hash, e.g. (continued from the example above):

1 App.ArticlesRoute = Ember.Route.extend({
2   queryParams: {
3     category: {
4       replace: true
5     }
6   }
7 });

Note that the name of this config property and its default value of false is similar to the link-to helper’s, which also lets you opt into a replaceState transition via replace=true.

Map a controller’s property to a different query param key

By default, specifying foo as a controller query param property will bind to a query param whose key is foo, e.g. ?foo=123. You can also map a controller property to a different query param key using the following configuration syntax:

1 App.ArticlesController = Ember.ArrayController.extend({
2   queryParams: {
3     category: "articles_category"
4   },
5   category: null
6 });

This will cause changes to the ArticlesController’s category property to update the articles_category query param, and vice versa.

Note that query params that require additional customization can be provided along with strings in the queryParams array.

1 App.ArticlesController = Ember.ArrayController.extend({
2   queryParams: [ "page", "filter", {
3     category: "articles_category"
4   }],
5   category: null,
6   page: 1,
7   filter: "recent"
8 });

Default values and deserialization

In the following example, the controller query param property page is considered to have a default value of 1.

1 App.ArticlesController = Ember.ArrayController.extend({
2   queryParams: 'page',
3   page: 1
4 });

This affects query param behavior in two ways:

  1. Query param values are cast to the same datatype as the default value, e.g. a URL change from /?page=3 to /?page=2 will set ArticlesController’s page property to the number 2, rather than the string "2". The same also applies to boolean default values.
  2. When a controller’s query param property is currently set to its default value, this value won’t be serialized into the URL. So in the above example, if page is 1, the URL might look like /articles, but once someone sets the controller’s page value to 2, the URL will become /articles?page=2.

Sticky Query Param Values

By default, query param values in Ember are “sticky”, in that if you make changes to a query param and then leave and re-enter the route, the new value of that query param will be preserved (rather than reset to its default). This is a particularly handy default for preserving sort/filter parameters as you navigate back and forth between routes.

Furthermore, these sticky query param values are remembered/restored according to the model loaded into the route. So, given a team route with dynamic segment /:team_name and controller query param “filter”, if you navigate to /badgers and filter by "rookies", then navigate to /bears and filter by "best", and then navigate to /potatoes and filter by "lamest", then given the following nav bar links,

1 {{#link-to 'team' 'badgers '}}Badgers{{/link-to}}
2 {{#link-to 'team' 'bears'   }}Bears{{/link-to}}
3 {{#link-to 'team' 'potatoes'}}Potatoes{{/link-to}}

the generated links would be

1 <a href="/badgers?filter=rookies">Badgers</a>
2 <a href="/bears?filter=best">Bears</a>
3 <a href="/potatoes?filter=lamest">Potatoes</a>

This illustrates that once you change a query param, it is stored and tied to the model loaded into the route.

If you wish to reset a query param, you have two options:

  1. explicitly pass in the default value for that query param into link-to or transitionTo
  2. use the Route.resetController hook to set query param values back to their defaults before exiting the route or changing the route’s model

In the following example, the controller’s page query param is reset to 1, while still scoped to the pre-transition ArticlesRoute model. The result of this is that all links pointing back into the exited route will use the newly reset value 1 as the value for the page query param.

1 App.ArticlesRoute = Ember.Route.extend({
2   resetController: function (controller, isExiting, transition) {
3     if (isExiting) {
4       // isExiting would be false if only the route's model was changing
5       controller.set('page', 1);
6     }
7   }
8 });

In some cases, you might not want the sticky query param value to be scoped to the route’s model but would rather reuse a query param’s value even as a route’s model changes. This can be accomplished by setting the scope option to "controller" within the controller’s queryParams config hash:

1 App.ArticlesRoute = Ember.Route.extend({
2   queryParams: {
3     showMagnifyingGlass: {
4       scope: "controller"
5     }
6   }
7 });

The following demonstrates how you can override both the scope and the query param URL key of a single controller query param property:

 1 App.ArticlesController = Ember.Controller.extend({
 2   queryParams: [ "page", "filter",
 3     {
 4       showMagnifyingGlass: {
 5         scope: "controller",
 6         as: "glass",
 7       }
 8     }
 9   ]
10 });

Examples

Asynchronous Routing

This section covers some more advanced features of the router and its capability for handling complex async logic within your app.

A Word on Promises…

Ember’s approach to handling asynchronous logic in the router makes heavy use of the concept of Promises. In short, promises are objects that represent an eventual value. A promise can either fulfill (successfully resolve the value) or reject (fail to resolve the value). The way to retrieve this eventual value, or handle the cases when the promise rejects, is via the promise’s then method, which accepts two optional callbacks, one for fulfillment and one for rejection. If the promise fulfills, the fulfillment handler gets called with the fulfilled value as its sole argument, and if the promise rejects, the rejection handler gets called with a reason for the rejection as its sole argument. For example:

 1 var promise = fetchTheAnswer();
 2 
 3 promise.then(fulfill, reject);
 4 
 5 function fulfill(answer) {
 6   console.log("The answer is " + answer);
 7 }
 8 
 9 function reject(reason) {
10   console.log("Couldn't get the answer! Reason: " + reason);
11 }

Much of the power of promises comes from the fact that they can be chained together to perform sequential asynchronous operations:

1 // Note: jQuery AJAX methods return promises
2 var usernamesPromise = Ember.$.getJSON('/usernames.json');
3 
4 usernamesPromise.then(fetchPhotosOfUsers)
5                 .then(applyInstagramFilters)
6                 .then(uploadTrendyPhotoAlbum)
7                 .then(displaySuccessMessage, handleErrors);

In the above example, if any of the methods fetchPhotosOfUsers, applyInstagramFilters, or uploadTrendyPhotoAlbum returns a promise that rejects, handleErrors will be called with the reason for the failure. In this manner, promises approximate an asynchronous form of try-catch statements that prevent the rightward flow of nested callback after nested callback and facilitate a saner approach to managing complex asynchronous logic in your applications.

This guide doesn’t intend to fully delve into all the different ways promises can be used, but if you’d like a more thorough introduction, take a look at the readme for RSVP, the promise library that Ember uses.

The Router Pauses for Promises

When transitioning between routes, the Ember router collects all of the models (via the model hook) that will be passed to the route’s controllers at the end of the transition. If the model hook (or the related beforeModel or afterModel hooks) return normal (non-promise) objects or arrays, the transition will complete immediately. But if the model hook (or the related beforeModel or afterModel hooks) returns a promise (or if a promise was provided as an argument to transitionTo), the transition will pause until that promise fulfills or rejects.

If the promise fulfills, the transition will pick up where it left off and begin resolving the next (child) route’s model, pausing if it too is a promise, and so on, until all destination route models have been resolved. The values passed to the setupController hook for each route will be the fulfilled values from the promises.

A basic example:

 1 App.TardyRoute = Ember.Route.extend({
 2   model: function() {
 3     return new Ember.RSVP.Promise(function(resolve) {
 4       Ember.run.later(function() {
 5         resolve({ msg: "Hold Your Horses" });
 6       }, 3000);
 7     });
 8   }, 
 9 
10   setupController: function(controller, model) {
11     console.log(model.msg); // "Hold Your Horses"
12   }
13 });

When transitioning into TardyRoute, the model hook will be called and return a promise that won’t resolve until 3 seconds later, during which time the router will be paused in mid-transition. When the promise eventually fulfills, the router will continue transitioning and eventually call TardyRoute’s setupController hook with the resolved object.

This pause-on-promise behavior is extremely valuable for when you need to guarantee that a route’s data has fully loaded before displaying a new template.

When Promises Reject…

We’ve covered the case when a model promise fulfills, but what if it rejects?

By default, if a model promise rejects during a transition, the transition is aborted, no new destination route templates are rendered, and an error is logged to the console.

You can configure this error-handling logic via the error handler on the route’s actions hash. When a promise rejects, an error event will be fired on that route and bubble up to ApplicationRoute’s default error handler unless it is handled by a custom error handler along the way, e.g.:

 1 App.GoodForNothingRoute = Ember.Route.extend({
 2   model: function() {
 3     return Ember.RSVP.reject("FAIL");
 4   },
 5 
 6   actions: {
 7     error: function(reason) {
 8       alert(reason); // "FAIL"
 9 
10       // Can transition to another route here, e.g.
11       // this.transitionTo('index');
12 
13       // Uncomment the line below to bubble this error event:
14       // return true;
15     }
16   }
17 });

In the above example, the error event would stop right at GoodForNothingRoute’s error handler and not continue to bubble. To make the event continue bubbling up to ApplicationRoute, you can return true from the error handler.

Recovering from Rejection

Rejected model promises halt transitions, but because promises are chainable, you can catch promise rejects within the model hook itself and convert them into fulfills that won’t halt the transition.

1 App.FunkyRoute = Ember.Route.extend({
2   model: function() {
3     return iHopeThisWorks().then(null, function() {
4       // Promise rejected, fulfill with some default value to
5       // use as the route's model and continue on with the transition
6       return { msg: "Recovered from rejected promise" };
7     });
8   }
9 });

beforeModel and afterModel

The model hook covers many use cases for pause-on-promise transitions, but sometimes you’ll need the help of the related hooks beforeModel and afterModel. The most common reason for this is that if you’re transitioning into a route with a dynamic URL segment via {{link-to}} or transitionTo (as opposed to a transition caused by a URL change), the model for the route you’re transitioning into will have already been specified (e.g. {{#link-to 'article' article}} or this.transitionTo('article', article)), in which case the model hook won’t get called. In these cases, you’ll need to make use of either the beforeModel or afterModel hook to house any logic while the router is still gathering all of the route’s models to perform a transition.

beforeModel

Easily the more useful of the two, the beforeModel hook is called before the router attempts to resolve the model for the given route. In other words, it is called before the model hook gets called, or, if model doesn’t get called, it is called before the router attempts to resolve any model promises passed in for that route.

Like model, returning a promise from beforeModel will pause the transition until it resolves, or will fire an error if it rejects.

The following is a far-from-exhaustive list of use cases in which beforeModel is very handy:

  • Deciding whether to redirect to another route before performing a potentially wasteful server query in model
  • Ensuring that the user has an authentication token before proceeding onward to model
  • Loading application code required by this route
1 App.SecretArticlesRoute  = Ember.Route.extend({
2   beforeModel: function() {
3     if (!this.controllerFor('auth').get('isLoggedIn')) {
4       this.transitionTo('login');
5     }
6   }
7 });

See the API Docs for beforeModel

afterModel

The afterModel hook is called after a route’s model (which might be a promise) is resolved, and follows the same pause-on-promise semantics as model and beforeModel. It is passed the already-resolved model and can therefore perform any additional logic that depends on the fully resolved value of a model.

 1 App.ArticlesRoute = Ember.Route.extend({
 2   model: function() {
 3     // App.Article.find() returns a promise-like object
 4     // (it has a `then` method that can be used like a promise)
 5     return App.Article.find();
 6   },
 7   afterModel: function(articles) {
 8     if (articles.get('length') === 1) {
 9       this.transitionTo('article.show', articles.get('firstObject'));
10     }
11   }
12 });

You might be wondering why we can’t just put the afterModel logic into the fulfill handler of the promise returned from model; the reason, as mentioned above, is that transitions initiated via {{link-to}} or transitionTo likely already provided the model for this route, so model wouldn’t be called in these cases.

See the API Docs for afterModel

More Resources

Loading / Error Substates

In addition to the techniques described in the Asynchronous Routing Guide, the Ember Router provides powerful yet overridable conventions for customizing asynchronous transitions between routes by making use of error and loading substates.

loading substates

The Ember Router allows you to return promises from the various beforeModel/model/afterModel hooks in the course of a transition (described here). These promises pause the transition until they fulfill, at which point the transition will resume.

Consider the following:

 1 App.Router.map(function() {
 2   this.resource('foo', function() { // -> FooRoute
 3     this.route('slowModel');        // -> FooSlowModelRoute
 4   });
 5 });
 6 
 7 App.FooSlowModelRoute = Ember.Route.extend({
 8   model: function() {
 9     return somePromiseThatTakesAWhileToResolve();
10   }
11 });

If you navigate to foo/slow_model, and in FooSlowModelRoute#model, you return an AJAX query promise that takes a long time to complete. During this time, your UI isn’t really giving you any feedback as to what’s happening; if you’re entering this route after a full page refresh, your UI will be entirely blank, as you have not actually finished fully entering any route and haven’t yet displayed any templates; if you’re navigating to foo/slow_model from another route, you’ll continue to see the templates from the previous route until the model finish loading, and then, boom, suddenly all the templates for foo/slow_model load.

So, how can we provide some visual feedback during the transition?

Ember provides a default implementation of the loading process that implements the following loading substate behavior.

1 App.Router.map(function() {
2   this.resource('foo', function() {       // -> FooRoute
3     this.resource('foo.bar', function() { // -> FooBarRoute
4       this.route('baz');                  // -> FooBarBazRoute
5     });
6   });
7 });

If a route with the path foo.bar.baz returns a promise that doesn’t immediately resolve, Ember will try to find a loading route in the hierarchy above foo.bar.baz that it can transition into, starting with foo.bar.baz’s sibling:

  1. foo.bar.loading
  2. foo.loading
  3. loading

Ember will find a loading route at the above location if either a) a Route subclass has been defined for such a route, e.g.

  1. App.FooBarLoadingRoute
  2. App.FooLoadingRoute
  3. App.LoadingRoute

or b) a properly-named loading template has been found, e.g.

  1. foo/bar/loading
  2. foo/loading
  3. loading

During a slow asynchronous transition, Ember will transition into the first loading sub-state/route that it finds, if one exists. The intermediate transition into the loading substate happens immediately (synchronously), the URL won’t be updated, and, unlike other transitions that happen while another asynchronous transition is active, the currently active async transition won’t be aborted.

After transitioning into a loading substate, the corresponding template for that substate, if present, will be rendered into the main outlet of the parent route, e.g. foo.bar.loading’s template would render into foo.bar’s outlet. (This isn’t particular to loading routes; all routes behave this way by default.)

Once the main async transition into foo.bar.baz completes, the loading substate will be exited, its template torn down, foo.bar.baz will be entered, and its templates rendered.

Eager vs. Lazy Async Transitions

Loading substates are optional, but if you provide one, you are essentially telling Ember that you want this async transition to be “eager”; in the absence of destination route loading substates, the router will “lazily” remain on the pre-transition route while all of the destination routes’ promises resolve, and only fully transition to the destination route (and renders its templates, etc.) once the transition is complete. But once you provide a destination route loading substate, you are opting into an “eager” transition, which is to say that, unlike the “lazy” default, you will eagerly exit the source routes (and tear down their templates, etc) in order to transition into this substate. URLs always update immediately unless the transition was aborted or redirected within the same run loop.

This has implications on error handling, i.e. when a transition into another route fails, a lazy transition will (by default) just remain on the previous route, whereas an eager transition will have already left the pre-transition route to enter a loading substate.

The loading event

If you return a promise from the various beforeModel/model/afterModel hooks, and it doesn’t immediately resolve, a loading event will be fired on that route and bubble upward to ApplicationRoute.

If the loading handler is not defined at the specific route, the event will continue to bubble above a transition’s pivot route, providing the ApplicationRoute the opportunity to manage it.

 1 App.FooSlowModelRoute = Ember.Route.extend({
 2   model: function() {
 3     return somePromiseThatTakesAWhileToResolve();
 4   },
 5   actions: {
 6     loading: function(transition, originRoute) {
 7       //displayLoadingSpinner();
 8 
 9       // Return true to bubble this event to `FooRoute`
10       // or `ApplicationRoute`.
11       return true;
12     }
13   }
14 });

The loading handler provides the ability to decide what to do during the loading process. If the last loading handler is not defined or returns true, Ember will perform the loading substate behavior.

 1 App.ApplicationRoute = Ember.Route.extend({
 2   actions: {
 3     loading: function(transition, originRoute) {
 4       displayLoadingSpinner();
 5       
 6       // substate implementation when returning `true`
 7       return true;
 8     }
 9   }
10 });

error substates

Ember provides an analogous approach to loading substates in the case of errors encountered during a transition.

Similar to how the default loading event handlers are implemented, the default error handlers will look for an appropriate error substate to enter, if one can be found.

1 App.Router.map(function() {
2   this.resource('articles', function() { // -> ArticlesRoute
3     this.route('overview');              // -> ArticlesOverviewRoute
4   });
5 });

For instance, an error thrown or rejecting promise returned from ArticlesOverviewRoute#model (or beforeModel or afterModel) will look for:

  1. Either ArticlesErrorRoute or articles/error template
  2. Either ErrorRoute or error template

If one of the above is found, the router will immediately transition into that substate (without updating the URL). The “reason” for the error (i.e. the exception thrown or the promise reject value) will be passed to that error state as its model.

If no viable error substates can be found, an error message will be logged.

error substates with dynamic segments

Routes with dynamic segments are often mapped to a mental model of “two separate levels.” Take for example:

 1 App.Router.map(function() {
 2   this.resource('foo', {path: '/foo/:id'}, function() {
 3     this.route('baz');
 4   });
 5 });
 6 
 7 App.FooRoute = Ember.Route.extend({
 8   model: function(params) {
 9     return new Ember.RSVP.Promise(function(resolve, reject) {
10        reject("Error");
11     });
12   }
13 });

In the URL hierarchy you would visit /foo/12 which would result in rendering the foo template into the application template’s outlet. In the event of an error while attempting to load the foo route you would also render the top-level error template into the application template’s outlet. This is intentionally parallel behavior as the foo route is never successfully entered. In order to create a foo scope for errors and render foo/error into foo’s outlet you would need to split the dynamic segment:

1 App.Router.map(function() {
2   this.resource('foo', {path: '/foo'}, function() {
3     this.resource('elem', {path: ':id'}, function() {
4       this.route('baz');
5     });
6   });
7 });

Example JSBin

The error event

If ArticlesOverviewRoute#model returns a promise that rejects (because, for instance, the server returned an error, or the user isn’t logged in, etc.), an error event will fire on ArticlesOverviewRoute and bubble upward. This error event can be handled and used to display an error message, redirect to a login page, etc.

 1 App.ArticlesOverviewRoute = Ember.Route.extend({
 2   model: function(params) {
 3     return new Ember.RSVP.Promise(function(resolve, reject) {
 4        reject("Error");
 5     });
 6   },
 7   actions: {
 8     error: function(error, transition) {
 9 
10       if (error && error.status === 400) {
11         // error substate and parent routes do not handle this error 
12         return this.transitionTo('modelNotFound');
13       }
14 
15       // Return true to bubble this event to any parent route.
16       return true;
17     }
18   }
19 });

In analogy with the loading event, you could manage the error event at the Application level to perform any app logic and based on the result of the last error handler, Ember will decide if substate behavior must be performed or not.

 1 App.ApplicationRoute = Ember.Route.extend({
 2   actions: {
 3     error: function(error, transition) {
 4 
 5       // Manage your errors
 6       Ember.onerror(error);
 7 
 8       // substate implementation when returning `true`
 9       return true;
10 
11     }
12   }
13 });

Legacy LoadingRoute

Previous versions of Ember (somewhat inadvertently) allowed you to define a global LoadingRoute which would be activated whenever a slow promise was encountered during a transition and exited upon completion of the transition. Because the loading template rendered as a top-level view and not within an outlet, it could be used for little more than displaying a loading spinner during slow transitions. Loading events/substates give you far more control, but if you’d like to emulate something similar to the legacy LoadingRoute behavior, you could do as follows:

 1 App.LoadingView = Ember.View.extend({
 2   templateName: 'global-loading',
 3   elementId: 'global-loading'
 4 });
 5 
 6 App.ApplicationRoute = Ember.Route.extend({
 7   actions: {
 8     loading: function() {
 9       var view = this.container.lookup('view:loading').append();
10       this.router.one('didTransition', view, 'destroy');
11     }
12   }
13 });

Example JSBin

This will, like the legacy LoadingRoute, append a top-level view when the router goes into a loading state, and tear down the view once the transition finishes.

Preventing And Retrying Transitions

During a route transition, the Ember Router passes a transition object to the various hooks on the routes involved in the transition. Any hook that has access to this transition object has the ability to immediately abort the transition by calling transition.abort(), and if the transition object is stored, it can be re-attempted at a later time by calling transition.retry().

Preventing Transitions via willTransition

When a transition is attempted, whether via {{link-to}}, transitionTo, or a URL change, a willTransition action is fired on the currently active routes. This gives each active route, starting with the leaf-most route, the opportunity to decide whether or not the transition should occur.

Imagine your app is in a route that’s displaying a complex form for the user to fill out and the user accidentally navigates backwards. Unless the transition is prevented, the user might lose all of the progress they made on the form, which can make for a pretty frustrating user experience.

Here’s one way this situation could be handled:

 1 App.FormRoute = Ember.Route.extend({
 2   actions: {
 3     willTransition: function(transition) {
 4       if (this.controller.get('userHasEnteredData') &&
 5           !confirm("Are you sure you want to abandon progress?")) {
 6         transition.abort();
 7       } else {
 8         // Bubble the `willTransition` action so that
 9         // parent routes can decide whether or not to abort.
10         return true;
11       }
12     }
13   }
14 });

When the user clicks on a {{link-to}} helper, or when the app initiates a transition by using transitionTo, the transition will be aborted and the URL will remain unchanged. However, if the browser back button is used to navigate away from FormRoute, or if the user manually changes the URL, the new URL will be navigated to before the willTransition action is called. This will result in the browser displaying the new URL, even if willTransition calls transition.abort().

Aborting Transitions Within model, beforeModel, afterModel

The model, beforeModel, and afterModel hooks described in Asynchronous Routing each get called with a transition object. This makes it possible for destination routes to abort attempted transitions.

1 App.DiscoRoute = Ember.Route.extend({
2   beforeModel: function(transition) {
3     if (new Date() < new Date("January 1, 1980")) {
4       alert("Sorry, you need a time machine to enter this route.");
5       transition.abort();
6     }
7   }
8 });

Storing and Retrying a Transition

Aborted transitions can be retried at a later time. A common use case for this is having an authenticated route redirect the user to a login page, and then redirecting them back to the authenticated route once they’ve logged in.

 1 App.SomeAuthenticatedRoute = Ember.Route.extend({
 2   beforeModel: function(transition) {
 3     if (!this.controllerFor('auth').get('userIsLoggedIn')) {
 4       var loginController = this.controllerFor('login');
 5       loginController.set('previousTransition', transition);
 6       this.transitionTo('login');
 7     }
 8   }
 9 });
10 
11 App.LoginController = Ember.Controller.extend({
12   actions: {
13     login: function() {
14       // Log the user in, then reattempt previous transition if it exists.
15       var previousTransition = this.get('previousTransition');
16       if (previousTransition) {
17         this.set('previousTransition', null);
18         previousTransition.retry();
19       } else {
20         // Default back to homepage
21         this.transitionToRoute('index');
22       }
23     }
24   }
25 });