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

$state.transitionTo does not work in a resolve #1737

Closed acollard closed 8 years ago

acollard commented 9 years ago

We conditionally redirect to different states in resolves. This worked well with angular routes but is failing in ui-router. It is a much cleaner approach than listening for the $stateChangeStarted event globally and making a decision based on which state we are going to. We also typically have multiple resolves each which could redirect. (ex. login, hasPermissions, etc.)

We do something like this (simplified example):

$stateProvider.state({ 
    name: 'home', 
    url: '/home', 
    resolve: {
      loginRequired: ['$q','$state', function($q, $state){
       if(!isLoggedIn) 
        {
          $state.go('login');
          return $q.reject('User not logged in. Redirecting to login page.');
        }
        // Just let it pass through if we are logged in
      }]
    }});

This results in an unknown state when we change to 'home' if not logged in.

Here is a quick breakdown of what is happening and why.

  1. We initially start going to state 'home'.
  2. During this process the 'home' resolves are run. Which starts another transition to 'login'.
  3. 'login' resolves are run and the $state.transition is set to the promise for the 'login' transition. We return from the $state.go('login') function.
  4. 'home' state resolves have now all run and the $state.transition is now set to the promise for the 'home' transition.
  5. 'home' promise gets an error because we intentionally returned $q.reject(). 'login' promise succeeds but then $state.transition is no longer the 'login' transition because 'home' just updated it in 4.

The issue is $state.transition is set after the resolves which causes transitions occurring inside a resolve to always get overwritten by their callers. The TransitionSuperseded is then incorrectly thrown.

Here is a plunker demonstrating the issue. http://plnkr.co/edit/wmPVv3R2BPenzcR4uINV

To fix this, the object used to determine if a transition has been superseded should be set prior to calling the state resolves.

paveleremin commented 9 years ago

do you have any updates on this issue?

acollard commented 9 years ago

@paveleremin Unfortunately this isn't fixed yet.

We use something like this.

loginRequired: ['$q','$state', function($q, $state){
       if(!isLoggedIn) 
        {
          $timeout(function(){$state.go('login');});
          return $q.reject('User not logged in. Redirecting to login page.');
        }
      }]

The timeout allows us to get out of the current call stack.

acollard commented 9 years ago

@nateabele Any chance we could include a fix for this issue in one of the next drops?

nateabele commented 9 years ago

Yeah, we have a set of totally new APIs to address stuff like this. Just trying to finalize them over the next ~week, then we'll probably push out a 1.0-alpha.

christopherthielen commented 9 years ago

UI-Router 1.0 preview is available: https://github.com/angular-ui/ui-router/tree/feature-1.0

jmayday commented 8 years ago

@christopherthielen link is broken

christopherthielen commented 8 years ago

Closing, because this use case can be accomplished using ui-router 1.0 transition hooks.

https://ui-router.github.io/guide/ng1/migrate-to-1_0