How to redirect in a ui-router resolver?

All we need is an easy explanation of the problem, so here it is.

I am trying to redirect inside a ui-router resolve and wanted to know if there is a way to reroute in a router resolver. Currently this does not work as one would think.

resolver(auth, $state){
   if(!auth.isLoggedIn()){
       $state.go('noLoggedInPath');
   }
}

How does one redirect in a resolver correctly ?

My temp hack is this but I am not comfortable with it.

resolver(auth, $state, $timeout){
   if(!auth.isLoggedIn()){
        $timeout(function () {

             $state.go('noLoggedInPath');
        }, 0);
   }
}

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Yauheni’s answer does work, but to get around the weird timeout thing, you can reject the promise, and catch that in the $stateChangeError event, and do your redirect there. Like so…

state('admin', {
  resolve: {
    auth: function(UserService, $q, permissionService) {
      var deferred = $q.defer();
      return UserService.load().then(function(user){
        if (permissionService.can(user, {goTo: state})) {
          return deferred.resolve({});
        } else {
          return deferred.reject({redirectTo: 'some_other_state'});
        }
      });
    }
  }
});

And then ui-router always broadcasts “stateChangeError”, so you can do something like this..

$rootScope.$on('$stateChangeError', function(evt, to, toParams, from, fromParams, error) {
  if (error.redirectTo) {
    $state.go(error.redirectTo);
  } else {
    $state.go('error', {status: error.status})
  }
})

Method 2

You can return a promise from your resolver function that will indicate whether to continue navigating to the state or not. If you decide to navigate somewhere else – reject the promise and specify the proper state:

resolver($q, $state, $timeout, auth) {
    var deferred = $q.defer();

    // $timeout is an example; it also can be an xhr request or any other async function
    $timeout(function() {
      if (!auth.isLoggedIn()) {
        // user is not logged, do not proceed
        // instead, go to a different page
        $state.go('noLoggedInPath');
        deferred.reject();
      } else {
        // everything is fine, proceed
        deferred.resolve();
      }
    });

    return deferred.promise;
}

Plunkr here.

UPD: updated code and added plunkr. Looks like it only works if you put it inside a timeout function.

Method 3

I use this

   function Check($state, $q) {
        var deferred = $q.defer();
        if (condition) {
            deferred.resolve();
        }
        else {
            deferred.reject();
        }
        return deferred.promise.catch(function () { $state.go('path'); });
    }

Method 4

This is what I actually do, and I can’t find a better solution

resolver($q, $timeout, myService) {
    if(!areParameterValuesValid() || !isEverythingLogicallyOk()){
        var deferred = $q.defer();
        $timeout(function() {
            $state.go('somewhere');
            deferred.reject();
        });
        return deferred.promise;
    } else {
        return myService.loadSomething(passingSomeParams);
    }        
}

Method 5

I think a much cleaner answer is to return an already rejected promise like so:

resolver(auth, $state, $q){
   if(!auth.isLoggedIn()){
       $state.go('noLoggedInPath');
       
       // Return rejected promise
       return $q.reject();
   }
   return true;
}

Method 6

You can use resolver promises from with the redirectTo configuration

   redirectTo: function($transition$) {
      return $transition$.injector().getAsync('isNew').then(function(isNew) {
        return isNew ? 'estate.edit' : 'estate.view';
      });
    },

Method 7

Using resolves for this didn’t quite work out for me. I adapted the logic from this post to execute a redirect function on the data property of the state object. You can then tap into the $stateChangeStart event on $rootScope from the run block of the module and use the injector service’s invoke method to the change the state based on the conditions you specify. Works nicely across modules as well.

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply