If you are like me and want to get your hands on something to hack around, I created this Ember Twiddle embedded below. Play around with it to get a better feel for Ember’s route hook order. Add your own custom logging, transitions, nested routes and other things.
The route hook lifecycle in Ember can be broken up into two distinct phases: the validation phase and the setup phase. These two phases are surrounded by two separate actions that get triggered within the context of the route: willTransition()
at the beginning and didTransition()
at the end after a successful transition.
Please note that below I will simply describe the most general route transition case. Additionally, I will outline, in call order, the major routing hooks which will be triggered for the route transition’s lifecycle. The major hooks being those which I believe are the most useful to understand.
Starting the Route Transition
Given that you will begin inside of a specific route within your Ember application and take one of the following actions to perform a route transition: click on a {{#link-to}}
Handlebars helper or click on a UI element which performs a programmatic transitionTo()
or replaceWith()
call to navigate into another route.
1. willTransition
Now, Ember begins to construct the route transition. The willTransition()
event gets fired with the transition
argument on the currently active routes. This is probably one of the best places to prevent the transition from occurring. For example, imagine your user is currently filling out some form data but clicks on a header navigation link by accident. In this hook you could perform a quick analysis for the non-submitted data and show a modal prompt for a much better user experience.
If the transition was not terminated then we proceed to the validation phase.
Validation Phase
The purpose of this phase is to resolve all model promises for new routes. You may also use these hooks as a great opportunity to redirect the user to a different route. Keep in mind, if any of these hooks return a promise then the transition will pause until that promise either fulfills or rejects. If the project rejects, an error()
event will trigger with the error
and transition
arguments from the current route context and will bubble up the entire router hierarchy.
2. beforeModel
The beforeModel()
hook gets called with a single transition
argument. This hook is best used as a place to redirect to another route before needing to execute the current route’s model()
hook — if you don’t need any information that is contained within that model. Also, this is a great place to fire some asynchronous operations before the current model is resolved.
NOTE: The transition
argument here and with each of the following hooks can be used to abort the current transition (e.g. an unauthenticated user accesses an authenticated route). You can also save this argument as a chunk of application state inside a service and use it again at a later point in time to retry
the previously aborted transition (e.g. deep-linking a user).
3. model
The model()
hook is called with the params
and transition
arguments and generally has one purpose to convert the URL into the model for this current route context. This usually means reaching into the Ember Data store to retrieve your model data.
// app/routes/post.js
import Ember from 'ember';
const { Route } = Ember;
export default Route.extend({
model(params/*, transition*/) {
return this.store.findRecord('post', params.post_id);
}
});
NOTE: If you transition into a route via a dynamic route segment through a transition and a model context is already provided — this hook is not called. A model context does not include a primitive string or number, which does cause the model hook to be called.
4. afterModel
The afterModel()
hook gets triggered next with the resolvedModel
and transition
arguments immediately after this route’s model has been resolved. This hook — along with the next hook — is best suited for performing any logic that must take place using information and data from this current model.
5. redirect
The redirect()
hook is nearly identical to it’s predecessor afterModel()
and gets called with the model
and transition
arguments. The important distinction here between these two hooks is simply that once you have reached the redirect()
hook in the lifecycle, the current route would now be considered “active” or “fully validated”.
// app/router.js
Router.map(function() {
this.route('posts', function() {
this.route('post', { path: '/:post_id' });
});
});
For example, if your current router hierarchy looks like the snippet above and we are currently in the context of the redirect()
hook of the posts
route. This means that if you now redirected into the child post
route, the beforeModel()
, model()
, and afterModel()
hooks for the posts
route will NOT be triggered again within the new redirecting transition — as opposed to actually being triggered again if you had instead redirected from the previous afterModel
hook in the posts
route.
Once we have completely resolved the prior redirect()
hook then we can begin to move forward and perform the setup phase.
Setup Phase
The purpose of this phase is to finally exit all of the previous routes and enter all of the new routes.
6. exit (private method)
The exit()
hook is actually a private method but I still find it useful when constructing a mental model about how each of these lifecycle pieces connect together. Ember does some internal work here to immediately trigger the next deactivate()
event as well as do some work to teardown views.
7. deactivate
The deactivate()
event is called with no arguments and it is executed when the router completely exits a route. Note that this hook will not execute when the model for the current route changes (e.g. going from post/1
to post/2
). deactivate()
is best used as place do any work immediately before leaving a certain route (e.g. tracking page leave analytics).
8. enter (private method)
The enter()
hook is a private method as well; however, it’s still useful to help gain a better understaning for the big picture. This hook’s only internal responsibility is to trigger the next event, activate()
.
9. activate
The activate()
event is called with no arguments and it is executed when the router enters the new route. In the past, similar to deactivate()
, I have found this to be a great place to trigger various analytics handlers.
10. setupController
The setupController()
hook is called with the controller
(controller for the current route) and model
arguments. This sole job of this hook is to set the model
property of the controller to the corresponding template property named model
.
I strongly avoid using this hook whenever possible because it has personally come back to bite me with some nightmare debugging scenarios. If I want to do something like alias my model name, then I will do this inside of the actual Controller instead.
WARNING: As with all Ember hooks, if you decide to implement the setupController()
hook inside your route, it will prevent the default behavior. To save yourself some headaches, remember to preserve that behavior and make sure to always call this._super(...arguments)
.
// app/route/application
import Ember from 'ember';
const { Route, set } = Ember;
export default Route.extend({
setupController(controller/*, model*/) {
// Hey Ember! Don't forget to do your stuff!
this._super(...arguments)
// OK good, Ember is done with it's internal work
// Now I'm going to ignore Alex's advice and do some stuff here anyway :)
set(controller, 'foo', 123);
}
});
11. renderTemplate
The renderTemplate()
hook gets called with the same controller
and model
arguments from the above setupController()
. Internally, this hook is used to render the template for your current route. By default, it renders your route’s template, configured with the current controller for the route.
Once again, I do strongly avoid using this hook whenever possible. In the past, I have done some complex custom rending logic here for multiple templates and transitions — it can become a pain to debug and refactor when you start to really hijack the things Ember does so well under the hood. A great rule of thumb here is to just follow the “Ember Way” as much as possible. These hooks are very powerful tools and with all of that power comes some big responsibilities.
Finishing the Route Transition
Finally the route transition has completed and Ember’s internal transition promise has successfully resolved.
12. didTransition
Last but not least, the didTransition
event gets fired with no arguments once the transition has successfully completed. This event is best used for some more analytics handing or setting/resetting state within the application.
Additionally, here are some great follow up resources to learn and play more with these concepts to help gain a more complete understanding:
- Ember’s Routing Guide
- Ember Diagonal by Alex Speller