- see https://gist.github.com/machty/5723945 for the latest API (unlikely to change from now on)
- latest Ember build: https://machty.s3.amazonaws.com/ember/ember-async-routing-10.js
Guide to latest API enhancements router.js PR Ember PR Latest Ember build with these changes
Below are some examples of how to use the newly enhanced async router API.
Catch the Transition in FormRoute
's willTransition
event handler
and call .abort()
on the provided transition.
Note: if you intercept a URL transition, your URL will get out of sync until the next successful transition. Not really sure there's a perfect solution for this for now, but either way, I'm punting on it for now.
Existing LoadingRoute
behavior is preserved: define LoadingRoute
which displays a spinner in enter/setup
and removes it in exit
.
e.g. we're entering the AdminRoute from some other route, and we want Admin route to put up some UI that says "Loading Admin Panel".
You can use the above setup, combined with logic in the destination route's validation hooks, e.g:
AdminRoute = Ember.Route.extend({
beforeModel: function() {
displayAdminRouteLoadingUI();
// Pretend this method exists. You can dig
// into container if you want.
this.routeFor('loading').one('doneLoading', this, function() {
hideAdminRouteLoadingUI();
});
}
});
Here's another approach that puts this logic in the model
hook (which
makes sense in this case since it's a non-dynamic route, which means you
can't pass a context to it via transitionTo).
This is beyond the scope of this iteration. Soon, though.
Use the beforeModel
hook (the first one fired when
transitioning into a route). Return a promise from this hook to pause
the transition while the code loads.
App.ArticlesRoute = Ember.Route.extend({
beforeModel: function() {
if (!App.Article) {
return $.getScript('/articles_code.js');
}
}
});
Basically, the beforeModel
in this case should augment global
state (e.g. make Articles code available to the container or global App
namespace or whatever) so that if the promise resolves, the model
hook
or the transitionTo-provided context should be able to resolve under the
assumption that that code has been loaded.
How do I redirect to a login form for an authenticated route and retry the original transition later?
The easiest way to do this is save the transition
object
passed to the beforeModel
hook before redirecting to
a login route. Then the login route can check for that saved
transition and resume it later.
App.AuthenticatedRoute = Ember.Route.extend({
beforeModel: function(transition) {
if (!authTokenPresent) {
return RSVP.reject();
// Could also just throw an error here too...
// it'll do the same thing as returning a rejecting promise.
// Note that we could put the redirecting `transitionTo`
// in here, but it's a better pattern to put this logic
// into `error` so that errors with resolving the model
// (say, the server tells us the auth token expired)
// can also get handled by the same redirect-to-login logic.
}
},
error: function(reason, transition) {
// This hook will be called for any errors / rejected promises
// from any of the other hooks or provided transitionTo promises.
// Redirect to `login` but save the attempted Transition
var loginController = this.controllerFor('login')
loginController.set('afterLoginTransition', transition);
this.transitionTo('login');
}
});
App.LoginController = Ember.Controller.extend({
loginSucceeded: function() {
var transition = this.get('afterLoginTransition');
if (transition) {
transition.retry();
} else {
this.transitionToRoute('welcome');
}
}
});
Check out the demo below which takes this approach:
Override willTransition
handler on leaf route where items are saved, save the
transition for later, and then retry it once saving is complete.
App.ThingRoute = Ember.Route.extend({
events: {
willTransition: function(transition) {
if (this.controller.get('isSaving')) {
this.controller.set('afterSaveTransition', transition);
}
}
}
});
App.ThingController = Ember.Controller.extend({
afterSave: function() {
var transition = this.get('afterSaveTransition');
if (transition) {
this.set('afterSaveTransition', null);
transition.retry();
} else {
this.transitionToRoute('default');
}
}
});
Here's the docs straight from the PR for the events
hash
in Ember.Route
:
/**
## Bubbling
By default, an event will stop bubbling once a handler defined
on the `events` hash handles it. To continue bubbling the event,
you must return `true` from the handler.
### `error`
When attempting to transition into a route, any of the hooks
may throw an error, or return a promise that rejects, at which
point an `error` event will be fired on the partially-entered
routes, allowing for per-route error handling logic, or shared
error handling logic defined on a parent route.
Here is an example of an error handler that will be invoked
for rejected promises / thrown errors from the various hooks
on the route, as well as any unhandled errors from child
routes:
```js
App.AdminRoute = Ember.Route.extend({
beforeModel: function() {
throw "bad things!";
// ...or, equivalently:
return Ember.RSVP.reject("bad things!");
},
events: {
error: function(error, transition) {
// Assuming we got here due to the error in `beforeModel`,
// we can expect that error === "bad things!",
// but a promise model rejecting would also
// call this hook, as would any errors encountered
// in `afterModel`.
// The `error` hook is also provided the failed
// `transition`, which can be stored and later
// `.retry()`d if desired.
this.transitionTo('login');
}
}
});
```
`error` events that bubble up all the way to `ApplicationRoute`
will fire a default error handler that logs the error. You can
specify your own global defaut error handler by overriding the
`error` handler on `ApplicationRoute`:
```js
App.ApplicationRoute = Ember.Route.extend({
events: {
error: function(error, transition) {
this.controllerFor('banner').displayError(error.message);
}
}
});
```
@see {Ember.Route#send}
@see {Handlebars.helpers.action}
@property events
@type Hash
@default null
*/