Skip to content

Expose $urlRouterUpdateStart event to allow deferrable update() #591

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 23, 2013
Merged

Expose $urlRouterUpdateStart event to allow deferrable update() #591

merged 1 commit into from
Nov 23, 2013

Conversation

rynz
Copy link

@rynz rynz commented Nov 20, 2013

In many complex applications the first question that is asked is how can I asynchronously request user information before any $stateChangeStart events trigger. Whether it be to incorporate route protection, configuration, redirection etc.

This proposed pull request seeks to answer that question by exposing a new event $urlRouterUpdateStart which can cancel $urlRouter#update() with preventDefault(). update() is then referenced in the $urlRouterUpdateStart event listener and can be called asynchronously/synchronously.

The following example deferrers update() for 2000ms using $timeout asynchronously. The listener's deregistration function is assigned to $off and called when the listener is fired so that the listener will only defer update() once.

angular.module('app', ['ui.router']);
  .run(function($rootScope, $timeout) {
    var $off = $rootScope.$on('$urlRouterUpdateStart', function(evt, update) {
      evt.preventDefault();
      $timeout(update, 2000);
      $off();
    });
  });

@timkindberg
Copy link
Contributor

I'm curious why this can't be done within $stateChangeStart. Is it because by then the url has already changed?

@rynz
Copy link
Author

rynz commented Nov 22, 2013

@timkindberg I looked at this event too and yes it's generally because the state transition promises etc are already set in motion and it was very difficult to inject a new promise to defer / replay the transition etc. Users may also want deferred logic that effects the when, otherwise and rule methods of $urlRouterProvider which happen before $stateChangeStart.

I also looked at $locationChangeStart in angular core but there was no way to replay this event without taking a lot of boilerplate code from AngularJS.

Definitely open to other implementations but I do think ui router would benefit greatly by having some sort of deferrable method to load user information / other configuration of an application before a state transition happens with the ability to choose if the deferrable method is run only once, or on every state change call etc.

I actually wonder if it would be possible / plausible to write into angular core an option of passing a promise to preventDefault(somePromise) that waits for the promise to resolve, then continue and if rejects acts as if the event was cancelled.

@nateabele
Copy link
Contributor

There are different ways to handle this use case, of which this is only one. For example, in apps I write, states that can't be transitioned to almost always require API calls that don't resolve until the user has authenticated (hence, the states that trigger them never resolve).

A solution that would be cleaner and require less overhead would be to have update() check $event.defaultPrevented, and provide a public alias to update(), i.e. $urlRouter.sync().

@rynz
Copy link
Author

rynz commented Nov 22, 2013

@nateabele Yes I used to take the "grandfather" approach by having a state that resolved it's dependencies first. However I've recently been working with route protection via $stateChangeStart event which is fired before dependencies of a state is resolved. As those are typically for the controller.

I prefer your proposed solution, updating PR now.

@rynz
Copy link
Author

rynz commented Nov 22, 2013

@nateabele Updated PR. Sorry my other machine obviously has git default setup incorrectly.

@nateabele
Copy link
Contributor

@2ix No worries. Sorry if I wasn't clear in my previous explanation, but the point was specifically to avoid the overhead of an extra event dispatch. In other words, $urlRouterUpdateStart shouldn't need to exist.

The idea was to use the existing event, as follows:

function update($event) {
  if ($event && $event.defaultPrevented) return;
  /* ... */
}

The current interception of $locationChangeSuccess doesn't need to change, you just need to intercept it yourself and preventDefault() before urlRouter does. Then, when you're done handling your user stuff, you can call sync() (which IMO should look like sync: function() { update(); } so as not to expose the internal function directly). In that case, $event will be undefined and execution will continue as normal.

Make sense?

@rynz
Copy link
Author

rynz commented Nov 23, 2013

@nateabele No worries, committed the change for you.

@nateabele
Copy link
Contributor

@2ix Looks perfect. Squash your commits & we'll roll it.

@rynz
Copy link
Author

rynz commented Nov 23, 2013

@nateabele Great! How's this?

nateabele added a commit that referenced this pull request Nov 23, 2013
Expose `$urlRouterUpdateStart` event to allow deferrable `update()`
@nateabele nateabele merged commit 8612380 into angular-ui:master Nov 23, 2013
@nateabele
Copy link
Contributor

@2ix 👍

@rynz
Copy link
Author

rynz commented Nov 23, 2013

@nateabele When do you expect this to be built into release for bower users?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants