Presently, handleURL and transitionTo behave very differently
when transitioning with an unresolved Promise object.
handleURL(which is invoked in response to URL changes) callsmodelon the routes with dynamic parameters (e.g.:post_id) to retrieve the context object. If the object is a Promise, the transition pauses and the Router goes into aLoadingRouteuntil the promise resolves (or fails).transitionTocan be called directly with any number of context objects. Regardless of whether these objects are promises or not, the transition will always proceed without pausing.
Since handleURL always pauses when a promise object is encountered,
and transitionTo never pauses, there's no clean way to handle both
kinds of async transitions: you're forced to use a global LoadingRoute
to (for example) display a loading spinner for URL transitions, but then
for direct transitions, you still have to handle the async-ness in your
templates, using {{#if isLoaded}} guards, and isLoaded is a property
that only exists for Ember Data objects, and not promises in general.
So, no customizability, and sealing your app requires messy duplication
of async handling.
In addition, there are problems with LoadingRoute, namely:
- There's only one, shared between all different kinds of paused transitions. Can't really customize nested routes.
- The
loadingview gets appended to<body>, after theapplicationtemplate. This might make the most sense as a default when there's only one LoadingRoute shared between the whole application, but a less surprising default would be to insert theloadingtemplate into the currently-loading outlet.
- Allow the developer to choose how to handle each transition on a
per-route basis. There are exactly two choices, and they are elected
as follows: If you transition into a route and the context for that
route is a promise, Ember will check if there is a LoadingRoute
defined for that substate (see below). If so, assume legacy
handleURLbehavior, pause the router, and resume when the promise is resolved. Otherwise, if no localLoadingRouteis found, continue with the transition (a la legacytransitionTo), with the option of using{{#if isLoaded}}. - Expose an
isLoadedcomputed property on the controller that returns false if context is an unresolved promise, then, once resolved, proxy to theisLoadedproperty on the context if it exists, or return true if it doesn't. The goal of this is to always guarantee such a property for the template to use, assuming the pass-thru transition approach is taken.isLoadingis presently only used in Ember-data, but this will extend the convention to the controller, which knows whether the context if an unresolved promise. - Allow the developer to specify local LoadingRoutes using standard
naming conventions. If
PostRoute'smodel()returns a promise, it'll check and see ifApp.PostLoadingRouteexists, and if so, render its template into the outlet that will eventually be used byPostRouteonce it loads, otherwise, continue transitioning. WhateverRouteyou provide can be easily overridden to return to the old behavior of rendering as a separate child of the application root element.
Planning on splitting this into 2 PRs:
- Unify
transitionToandhandleURLbehavior, preserve singleLoadingRoute. Whether it'stransitionToorhandleURL, if a promise is the context, we'll check to see if there's aloadingstate in router.js, and if so, pause the transition and go into the loading state, else just proceed with the transition and pass thru the promise context. - Add local
LoadingRoutesubstate behavior
router.js will need a (likely simplifying) overhaul to unify
collectObjects and setupContexts so that both handleURL and
transitionTo follow the same logic of pausing when necessary. Also,
the loading() function will need to construct an expected LoadingRoute
name that the getHandlerFunction in Ember can adequately respond to to
look up a Loading sub-route, if it exists.
The changes are simpler on the Ember side of things. The main trickery
involves adding an isLoaded property to ControllerMixin, which'll
probably look something like:
isLoaded: Ember.computed('content.isLoaded', '_promiseResolved', function(key) {
if(this.get('_promiseResolved')) {
return this.get('content.isLoaded') === false ? false : true;
} else {
return false;
}
})Then in the Router's deserialize function, if model returned a promise,
it'll make a note to set _promiseResolved to false on the controller,
and append a .then to set it to true when the promise is resolved.