Instantly share code, notes, and snippets.

Embed
What would you like to do?
How to do cool stuff with the new Router API

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
*/
@boy-jer

This comment has been minimized.

Show comment
Hide comment
@boy-jer

boy-jer Jun 17, 2013

Great effort @matchy. In your demo for 'How do I redirect to a login form', you used loginController.set('afterLoginTransition', transition);, where is afterLoginTransition property defined. I can't find it in your demo. I skimmed through the merged source for asyc router and still didn't see it. Also where is afterSaveTransition ans also the hasLoggedIn property used here ** if (!loginController.get('hasLoggedIn')) ** come from or where are this property defined.

Thanks.

boy-jer commented Jun 17, 2013

Great effort @matchy. In your demo for 'How do I redirect to a login form', you used loginController.set('afterLoginTransition', transition);, where is afterLoginTransition property defined. I can't find it in your demo. I skimmed through the merged source for asyc router and still didn't see it. Also where is afterSaveTransition ans also the hasLoggedIn property used here ** if (!loginController.get('hasLoggedIn')) ** come from or where are this property defined.

Thanks.

@wouterw

This comment has been minimized.

Show comment
Hide comment
@wouterw

wouterw Jun 17, 2013

@boy-jer the call to set defines it.

wouterw commented Jun 17, 2013

@boy-jer the call to set defines it.

@boy-jer

This comment has been minimized.

Show comment
Hide comment
@boy-jer

boy-jer Jun 18, 2013

Thanks @wouterw. I can see now that they are "virtual" properties that are not predefined on the object but via the unknownProperty() handler.

boy-jer commented Jun 18, 2013

Thanks @wouterw. I can see now that they are "virtual" properties that are not predefined on the object but via the unknownProperty() handler.

@seanrucker

This comment has been minimized.

Show comment
Hide comment
@seanrucker

seanrucker Jun 18, 2013

This looks stellar! Is this available in the latest version of ember or is it yet to be merged?

seanrucker commented Jun 18, 2013

This looks stellar! Is this available in the latest version of ember or is it yet to be merged?

@machty

This comment has been minimized.

Show comment
Hide comment
@machty

machty Jun 18, 2013

@seanrucker it's on ember master, but not in an RC yet

Owner

machty commented Jun 18, 2013

@seanrucker it's on ember master, but not in an RC yet

@workmanw

This comment has been minimized.

Show comment
Hide comment
@workmanw

workmanw Jun 19, 2013

@machty tweeted this out a few days back. Seems to help clarify the inner workings of this change: http://f.cl.ly/items/2J1h3m0C2t3n0d3x2b1p/AsyncRouterLinkTo.jpg

workmanw commented Jun 19, 2013

@machty tweeted this out a few days back. Seems to help clarify the inner workings of this change: http://f.cl.ly/items/2J1h3m0C2t3n0d3x2b1p/AsyncRouterLinkTo.jpg

@kylenathan

This comment has been minimized.

Show comment
Hide comment
@kylenathan

kylenathan Jun 26, 2013

@machty seems to be an issue if you retry an original transition when it was to a dynamic route that was directly loaded. It seems that because the model for the original route was not loaded and the transition was aborted, when the transition is retried, it has no model and fails.

E.g. Adjust your example for "How do I redirect to a login form for an authenticated route and retry the original transition later?" so that there is a dynamic route with the path "/article/:article_id" that uses the auth mixin.

If you then attempt to

  • directly load 'myapp.com/article/1' in your browser
  • you get redirected to login before article with id 1 is loaded
  • on logging in, the saved transition is retried but fails. I think this is because it is expecting the loaded model to be passed with the transition?

Apologies - can't knock up a jsbin as it needs the dynamic path to be the entry point.

kylenathan commented Jun 26, 2013

@machty seems to be an issue if you retry an original transition when it was to a dynamic route that was directly loaded. It seems that because the model for the original route was not loaded and the transition was aborted, when the transition is retried, it has no model and fails.

E.g. Adjust your example for "How do I redirect to a login form for an authenticated route and retry the original transition later?" so that there is a dynamic route with the path "/article/:article_id" that uses the auth mixin.

If you then attempt to

  • directly load 'myapp.com/article/1' in your browser
  • you get redirected to login before article with id 1 is loaded
  • on logging in, the saved transition is retried but fails. I think this is because it is expecting the loaded model to be passed with the transition?

Apologies - can't knock up a jsbin as it needs the dynamic path to be the entry point.

@domasx2

This comment has been minimized.

Show comment
Hide comment
@domasx2

domasx2 Jul 4, 2013

I have save problem, when retrying transition for "/article/1" it goes to "/article/undefined", not sure how to fix this

domasx2 commented Jul 4, 2013

I have save problem, when retrying transition for "/article/1" it goes to "/article/undefined", not sure how to fix this

@myfreeweb

This comment has been minimized.

Show comment
Hide comment
@myfreeweb

myfreeweb Jul 4, 2013

+1. Refreshing "/things/bd1e22f0-9a7b-44eb-ae41-0358f338a634" results in undefined (with ember-auth).

myfreeweb commented Jul 4, 2013

+1. Refreshing "/things/bd1e22f0-9a7b-44eb-ae41-0358f338a634" results in undefined (with ember-auth).

@jaaksarv

This comment has been minimized.

Show comment
Hide comment
@jaaksarv

jaaksarv Jul 5, 2013

@machty thanks for the hard work, now we can really start implementing proper authentication logic.

But I have same problem as @kylenathan and @domasx2. When I go directly to dynamic nested route eg. /articles/1 then I get nicely redirected to login. The transition passed to login is also correct and has article_id=1 passed as param. But when the login resolves both the ArticlesRoute.model and ArticleRoute.model gets called. The later is called with article_id==undefined. If I ignore the error then it works after all articles are loaded by ArticlesRoute.model, but I think that the ArticleRoute.model should not get called at all as we area already loading all models in ArticlesRoute.model.

jaaksarv commented Jul 5, 2013

@machty thanks for the hard work, now we can really start implementing proper authentication logic.

But I have same problem as @kylenathan and @domasx2. When I go directly to dynamic nested route eg. /articles/1 then I get nicely redirected to login. The transition passed to login is also correct and has article_id=1 passed as param. But when the login resolves both the ArticlesRoute.model and ArticleRoute.model gets called. The later is called with article_id==undefined. If I ignore the error then it works after all articles are loaded by ArticlesRoute.model, but I think that the ArticleRoute.model should not get called at all as we area already loading all models in ArticlesRoute.model.

@jaaksarv

This comment has been minimized.

Show comment
Hide comment
@jaaksarv

jaaksarv Jul 5, 2013

I dug deeper into the problem is in ember-data 0.13 that is not using promises yet. So calling just 'return App.Article.find()' from the model hook will trigger the afterModel instantly. Is this correct? Any workarounds until we get next version of ember-data?

jaaksarv commented Jul 5, 2013

I dug deeper into the problem is in ember-data 0.13 that is not using promises yet. So calling just 'return App.Article.find()' from the model hook will trigger the afterModel instantly. Is this correct? Any workarounds until we get next version of ember-data?

@jaaksarv

This comment has been minimized.

Show comment
Hide comment
@jaaksarv

jaaksarv Jul 8, 2013

I created a JSBin of the problem.
http://jsbin.com/efulor/1#/articles/1
After login we should be redirected back to /articles/1, but instead it redirects to /articles/undefined

jaaksarv commented Jul 8, 2013

I created a JSBin of the problem.
http://jsbin.com/efulor/1#/articles/1
After login we should be redirected back to /articles/1, but instead it redirects to /articles/undefined

@jaaksarv

This comment has been minimized.

Show comment
Hide comment
@jaaksarv

jaaksarv Jul 8, 2013

Second problem I have with ember-data and afterLogin hook.
http://jsbin.com/umoqob/1/
The articles route should redirect to first article, but it is not working on first load. When visiting articles route second time (going back to index for example) then works fine.

jaaksarv commented Jul 8, 2013

Second problem I have with ember-data and afterLogin hook.
http://jsbin.com/umoqob/1/
The articles route should redirect to first article, but it is not working on first load. When visiting articles route second time (going back to index for example) then works fine.

@machty

This comment has been minimized.

Show comment
Hide comment
@machty

machty Jul 9, 2013

@jaaksarv @kylenathan @domasx2 Can you confirm you still run into this on ember master?

Owner

machty commented Jul 9, 2013

@jaaksarv @kylenathan @domasx2 Can you confirm you still run into this on ember master?

@jaaksarv

This comment has been minimized.

Show comment
Hide comment
@jaaksarv

jaaksarv Jul 10, 2013

@machty Is there a nighly build I can check out? I currently don't have tools for building Ember master, but I can install if needed. You can easily check it out yourself also by replacing the ember version in my jsbins.

jaaksarv commented Jul 10, 2013

@machty Is there a nighly build I can check out? I currently don't have tools for building Ember master, but I can install if needed. You can easily check it out yourself also by replacing the ember version in my jsbins.

@bschaeffer

This comment has been minimized.

Show comment
Hide comment
@bschaeffer

bschaeffer Jul 11, 2013

This is great.... but how do you handle Router 404s, not just model/promise errors?

bschaeffer commented Jul 11, 2013

This is great.... but how do you handle Router 404s, not just model/promise errors?

@jaaksarv

This comment has been minimized.

Show comment
Hide comment
@jaaksarv

jaaksarv Jul 17, 2013

@machty I tried out with ember-latest. The login redirect problem is now solved:
http://jsbin.com/efulor/8#/articles/1

But I still have a problem with redirect to first article from afterLogin hook. Maybe doing something wrong.
http://jsbin.com/umoqob/3

Thanks for helping:)

jaaksarv commented Jul 17, 2013

@machty I tried out with ember-latest. The login redirect problem is now solved:
http://jsbin.com/efulor/8#/articles/1

But I still have a problem with redirect to first article from afterLogin hook. Maybe doing something wrong.
http://jsbin.com/umoqob/3

Thanks for helping:)

@andreisoare

This comment has been minimized.

Show comment
Hide comment
@andreisoare

andreisoare Jul 20, 2013

@jaaksarv isn't this the issue you're having? emberjs/data#1013

andreisoare commented Jul 20, 2013

@jaaksarv isn't this the issue you're having? emberjs/data#1013

@samselikoff

This comment has been minimized.

Show comment
Hide comment
@samselikoff

samselikoff Jul 23, 2013

I'm having trouble getting the global loading route working. It works on initial app load, but it doesn't show for the rest of my transitions. I'm using transitionToRoute in a view, rather than a {{#linkTo}} helper... is that a problem?

edit: didn't realize the LoadingRoute isn't a 'child' of the ApplicationRoute. I'm all set.

samselikoff commented Jul 23, 2013

I'm having trouble getting the global loading route working. It works on initial app load, but it doesn't show for the rest of my transitions. I'm using transitionToRoute in a view, rather than a {{#linkTo}} helper... is that a problem?

edit: didn't realize the LoadingRoute isn't a 'child' of the ApplicationRoute. I'm all set.

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