Skip to content

Instantly share code, notes, and snippets.

@machty

machty/unify.md Secret

Last active December 14, 2015 19:09
Show Gist options
  • Select an option

  • Save machty/5c6d0312baca15e036b4 to your computer and use it in GitHub Desktop.

Select an option

Save machty/5c6d0312baca15e036b4 to your computer and use it in GitHub Desktop.
proposal to unify `handleURL` and `transitionTo` in the router

Proposal: allow greater control over and unify behavior of handleURL and transitionTo

Presently, handleURL and transitionTo behave very differently when transitioning with an unresolved Promise object.

  • handleURL (which is invoked in response to URL changes) calls model on 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 a LoadingRoute until the promise resolves (or fails).
  • transitionTo can 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:

  1. There's only one, shared between all different kinds of paused transitions. Can't really customize nested routes.
  2. The loading view gets appended to <body>, after the application template. 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 the loading template into the currently-loading outlet.

Solution

  1. 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 handleURL behavior, pause the router, and resume when the promise is resolved. Otherwise, if no local LoadingRoute is found, continue with the transition (a la legacy transitionTo), with the option of using {{#if isLoaded}}.
  2. Expose an isLoaded computed property on the controller that returns false if context is an unresolved promise, then, once resolved, proxy to the isLoaded property 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. isLoading is presently only used in Ember-data, but this will extend the convention to the controller, which knows whether the context if an unresolved promise.
  3. Allow the developer to specify local LoadingRoutes using standard naming conventions. If PostRoute's model() returns a promise, it'll check and see if App.PostLoadingRoute exists, and if so, render its template into the outlet that will eventually be used by PostRoute once it loads, otherwise, continue transitioning. Whatever Route you provide can be easily overridden to return to the old behavior of rendering as a separate child of the application root element.

Implementation Details

Planning on splitting this into 2 PRs:

  1. Unify transitionTo and handleURL behavior, preserve single LoadingRoute. Whether it's transitionTo or handleURL, if a promise is the context, we'll check to see if there's a loading state 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.
  2. Add local LoadingRoute substate behavior

router.js

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.

Ember

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment