Don't use afterModel for redirection
09 February 2018
An advantage about going through my book for a rewrite is that I am inclined to review things I wouldn't when doing simple version upgrades. In the below post I want to show you one of the things I learned this way.
Redirecting during a route transition
If you want to redirect your app to another route while you're already in the middle of a transition, there are two scenarios.
If you don't need the resolved model
If you don't need to resolve the model to decide whether to redirect (or where to redirect), you should put the redirection in the beforeModel
hook which is the first one to run. The classic example is protecting an authenticated route:
If you need the resolved model
If you need to know what the model is to be able to decide on the redirection, you need a hook that runs after the model
hook. I've so far used the afterModel
hook for this purpose that gets passed the resolved model.
The official guides use the "redirect to the only existing blog post" example. I'm going to use another one - not because there's anything wrong with this one, but because that's the one in my book.
When clicking a band link, we want to see the band's details page if the band has a description - otherwise we want to see the list of the band's songs.
Here's what the template with the band link looks like:
Let's now see the corresponding route hook that implement the redirection:
1// app/routes/bands/band.js 2export default Route.extend({ 3 model(params) { 4 return this.store.findRecord('band', params.id); 5 }, 6 7 afterModel(band) { 8 if (band.get('description')) { 9 this.transitionTo('bands.band.details'); 10 } else { 11 this.transitionTo('bands.band.songs'); 12 } 13 } 14});
This works fine but let's see the route transitions in detail.
Logging out route transitions
Route transitions are logged verbosely if you enable the following lines in your configuration:
Also, you shouldn't forget to show debug messages in your browser console:
afterModel is wasteful, ...
Ok, let's see what route transitions and hooks are executed when we trigger a redirection:
1Attempting transition to bands.band 2Preparing to transition from 'bands.index' to 'bands.band.index' 3Transition #3: bands.band: calling beforeModel hook 4Transition #3: bands.band: calling deserialize hook 5Transition #3: bands.band: calling afterModel hook 6Attempting transition to bands.band.details 7Transition #3: bands.band.index: transition was aborted 8Transition #4: bands.band: calling beforeModel hook 9Transition #4: bands.band: calling deserialize hook 10Transition #3: detected abort. 11Transition #4: bands.band: calling afterModel hook 12Attempting transition to bands.band.details 13Transition #4: bands.band.details: calling beforeModel hook 14Transition #4: bands.band.details: calling deserialize hook 15Transition #4: bands.band.details: calling afterModel hook 16Transition #4: Resolved all models on destination route; finalizing transition. 17Transition #4: TRANSITION COMPLETE. 18XHR finished loading: GET "http://rarwe.local/bands/pearl-jam". 19XHR finished loading: GET "http://rarwe.local/bands/pearl-jam".
(deserialize
is just an alias for model
, so read it as such.)
You can see that the first transition, to bands.band.index
, is aborted because of the this.transitionTo
in the afterModel
of the bands.band
route and then a new one is attempted to bands.band.details
. However, all three model hooks are run again for the bands.band
route (see lines 8-11).
Also note that there are two ajax requests going out. The store.findRecord('band', ...)
call is responsible for this, as it will fetch the band from the backend (in the background if it has found it in the store so as not to block page rendering).
Since the model hook of the bands.band
route was fired twice, the ajax request is fired twice, too.
... so use the redirect hook
There is another, possibly less known route hook called redirect
, which is more suitable for redirection. I know, who would've thought, right?
I thought it was basically an alias for afterModel
, but there is a very important difference in their behavior. The source code shines a light on this distinction (see here):
redirect
andafterModel
behave very similarly and are called almost at the same time, but they have an important distinction in the case that, from one of these hooks, a redirect into a child route of this route occurs: redirects fromafterModel
essentially invalidate the current attempt to enter this route, and will result in this route'sbeforeModel
,model
, andafterModel
hooks being fired again within the new, redirecting transition. Redirects that occur within theredirect
hook, on the other hand, will not cause these hooks to be fired again the second time around; in other words, by the time theredirect
hook has been called, both the resolved model and attempted entry into this route are considered to be fully validated.
Let's thus change our afterModel
hook to redirect
:
Let's see the route transition details again:
1Attempting transition to bands.band 2Preparing to transition from 'bands.index' to 'bands.band.index' 3Transition #1: bands.band: calling beforeModel hook 4Transition #1: bands.band: calling deserialize hook 5Transition #1: bands.band: calling afterModel hook 6Attempting transition to bands.band.details 7Transition #1: bands.band.index: transition was aborted 8Transition #1: detected abort. 9Transition #2: bands.band.details: calling beforeModel hook 10Transition #2: bands.band.details: calling deserialize hook 11Transition #2: bands.band.details: calling afterModel hook 12Transition #2: Resolved all models on destination route; finalizing transition. 13Transitioned into 'bands.band.details' 14Transition #2: TRANSITION COMPLETE. 15XHR finished loading: GET "http://rarwe.local/bands/pearl-jam".
The model hooks for bands.band
are not fired a second time, and so there is only one ajax request hitting the backend and less time spent in model hooks (and consequently the page can be rendered faster).
So, in summary, don't use the afterModel for redirection. Use redirect.
Share on Twitter