angular-ui / ui-router

The de-facto solution to flexible routing with nested views in AngularJS
http://ui-router.github.io/
MIT License
13.53k stars 3k forks source link

Route interceptors in the style of http interceptors #788

Closed michalc closed 10 years ago

michalc commented 10 years ago

I think it would be useful to be able to add promise-based interceptors for route/stage changes, just like http interceptors. The current setup of using events seems just a little bit limited for anything but simple situations. Use cases:

  $state.go('newroute').catch(function(reason) {
    // Show the user the reason
  });

I feel there are likely others that I haven't thought of.

nateabele commented 10 years ago

I'm not sure I understand what this is adding to the current API. State transitions are already promise-based, and failures already receive an Error object with a reason message.

michalc commented 10 years ago

Ah... in that case my second example might not be adding anything, if it's already possible.

However, I think my first use-case/point still might be useful. $state.go is promise-based, but then you have an event-based API to configure the different cases of success/error, which (at least appear to be) synchronous in nature, and don't seem to have a built-in way to defer the route change, or to reject it later. So I'm wondering if something like the following could be possible to define:

'stateChangeStart': function(stateChangeDetails) {
  // Say if the user definitely isn't authorised
  return $q.reject('No authorisation');

  // Or maybe show a login form, which still then allows the route change to fail later
  return $modal.show('login');

  // Or even redirect to another route
  return $state.go('newroute');
},
'stageChangeError': function(rejection) {
  // Maybe show global error page?
  return $state.go('error');
}
jezstephens commented 10 years ago

I agree this would be useful. The use case I have in mind is similar to @michalc's.

I want to configure a listener or interceptor to check whether an "authenticate" flag has been set on the target state. If the flag is set, the interceptor checks whether the user is logged in. If the user is not logged in locally, a request is made to the server. If the server responds with a 401, an $http interceptor displays a login form. Once the user has logged in successfully, the chain of promises is resolved and the target state is entered.

Currently this can be achieved by adding a resolve function to each state requiring authentication, but I think it would be good if this could be made easier and more declarative (e.g. with a flag, as above).

I can't see a way to do this with the events which already exist:

module.run(function($rootScope, auth) {
  $rootScope.$on('$stateChangeStart', function(event, current, previous) {
    if (!current.authenticate) return;
    // auth.getUser() may make a request to the server and returns a promise.
    auth.getUser().then(function() {
      // proceed to "current". (oh, we're already there.)
    }).catch(function() {
      // stay at "previous". (oops, too late...)
    });
  });
});

Alternatively, maybe this can be achieved by adding a "private" resolve function dynamically in a builder decorator if the flag is set? I haven't tried that yet, but it sounds a bit dirty.

timkindberg commented 10 years ago

We are kind of already talking about this over here: https://github.com/angular-ui/ui-router/issues/618#issuecomment-30869009.

I'd say this is a dupe. Please read that post (at least from that comment) and then add your thoughts there.