Skip to content

Instantly share code, notes, and snippets.

@millisami
Forked from machty/new-router-examples.md
Created March 17, 2014 08:18
Show Gist options
  • Save millisami/9595673 to your computer and use it in GitHub Desktop.
Save millisami/9595673 to your computer and use it in GitHub Desktop.

Update 6/14/13

Router Facelift Examples

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.

How do I prevent a transition when a form is half filled out?

Catch the Transition in FormRoute's willTransition event handler and call .abort() on the provided transition.

Demo

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.

How do I put up a (global) Loading Spinner during a transition w/ Promises?

Existing LoadingRoute behavior is preserved: define LoadingRoute which displays a spinner in enter/setup and removes it in exit.

Demo

How can I let the destination route define the UI?

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).

Demo

How do I approximate the pivot loading behavior of old router?

This is beyond the scope of this iteration. Soon, though.

How do I load code async?

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.

Demo

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:

Demo

How do I disable transitions while saving?

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');
    }
  }
});

Demo

How do I handle errors?

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
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment