最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Defer Angular UI Router $stateChangeStart until server authorization response receieved - Stack Overflow

programmeradmin4浏览0评论

I have an Angular app using UI Router where I'm trying to validate a user's token, if one exists, when the app runs. I am also checking that the user has permission to access certains routes. The problem is that $stateChangeStart is running before I receive the response back from the authorization endpoint. Here's some code (coffeescript with js below) - this is all within my run block.

app.run(($rootScope, $state, $stateParams, $log, Auth) ->

  currentState = 'home'

  $rootScope.$state = $state

  # read a cookie if cookie exists
  if Auth.setAuthenticationToken()
    # hit api endpoint to validate token
    Auth.validateToken (user) ->
      # route to current state
      # this returns after $stateChangeStart runs below
      $state.go(currentState)

  $rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams) ->

    currentState = toState.name

    Auth.setAuthenticationToken()

    $rootScope.error = null

    # pare users access permissions with ining route's access level
    if (!Auth.authorize toState.data.access, Auth.user)
      event.preventDefault()
      $rootScope.error = "Sorry, you haven't provided the required credentials."
      $log.warn $rootScope.error
)

My question is how can I get the $stateChangeStart to run only after the response from the auth endpoint has been returned. This only needs to happen the first time. Every subsequent state change can be done without hitting the auth endpoint.

I have an Angular app using UI Router where I'm trying to validate a user's token, if one exists, when the app runs. I am also checking that the user has permission to access certains routes. The problem is that $stateChangeStart is running before I receive the response back from the authorization endpoint. Here's some code (coffeescript with js below) - this is all within my run block.

app.run(($rootScope, $state, $stateParams, $log, Auth) ->

  currentState = 'home'

  $rootScope.$state = $state

  # read a cookie if cookie exists
  if Auth.setAuthenticationToken()
    # hit api endpoint to validate token
    Auth.validateToken (user) ->
      # route to current state
      # this returns after $stateChangeStart runs below
      $state.go(currentState)

  $rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState, fromParams) ->

    currentState = toState.name

    Auth.setAuthenticationToken()

    $rootScope.error = null

    # pare users access permissions with ining route's access level
    if (!Auth.authorize toState.data.access, Auth.user)
      event.preventDefault()
      $rootScope.error = "Sorry, you haven't provided the required credentials."
      $log.warn $rootScope.error
)

My question is how can I get the $stateChangeStart to run only after the response from the auth endpoint has been returned. This only needs to happen the first time. Every subsequent state change can be done without hitting the auth endpoint.

Share Improve this question asked Jan 25, 2014 at 20:11 axelavaxelav 4353 silver badges11 bronze badges 2
  • 1 I have the same issue. Did you e up with a solution? – Erik Olson Commented Apr 29, 2014 at 14:43
  • 2 I also have a similar issue. I want the $rootScope.$on('$stateChangeStart', function(){} to execute only after a resolve in one of ui-router states. Problem is that the event $stateChangeStart triggers before the resovle can execute. Did you ever hit on a solution? – chuck w Commented Sep 12, 2014 at 1:39
Add a ment  | 

4 Answers 4

Reset to default 6

I'd create a function in your Auth service that returns a promise. Later, resolve (authorized) or reject (not authrized) that deferred. Then use it on the resolve property of your route definitions

$stateProvider.state('stateName',{
    ...
    ...
    resolve: {
         isAuthorized: function(Auth){
             return Auth.checkAuthorization();
         }
    }
})

To support subsequent checks you could maintain a promise within the service This might look like:

myApp.service('Auth',function($http,$q){
    var authStatusDeferred = $q.defer();
    this.checkAuthorization = function(){
        return authStatusDeferred.promise;
    };

    this.validateToken = function(user){
        var isValid = false;
        //..do validation stuff
        if(isValid) authStatusDeferred.resolve();
        //Otherwise state change will not happen..            
    };



});

oh, sorry about no coffee

I guess it's too late for answering this question, but I want to ment here for someone who may need a little idea.

I've tried to do almost same thing for a long time and I stopped doing that. I don't think stateChangeStart listener is reliable to use. I think the best way to do your what you want is decorating $state.go using $provide service. Here is an example:

/* config your angular app */
.config(function($provide) {
    $provide.decorator('$state', function($delegate) {

        var myState = $delegate;
        // Rename original 'go' method
        myState.originalGo = myState.go;

        myState.go = function(toState, toParams, options) {

        /*  
         *  Do what you want to do :)
         */

        this.originalGo(toState, toParams, options);
    };

    return $delegate;
  });

With this approach, you can do whatever you want before $stateChangeStart. I hope it helps.

I have found a very good way to make this work properly and not rely on resolve at all in another answer here. Here is the code:

rootScope.$on("$stateChangeStart", function (event, toState, toParams, fromState) {

    if (dataService.isInitialized()) {
        proceedAsUsual(); // Do the required actions based on the data you received in the service
    } 
    else {

        event.preventDefault();

        dataService.intialize().success(function () {
                $state.go(toState, toParams);
        });
    }
});

Then you can just remember that your data is already initialized in the service the way you like, e.g.:

function dataService() {

    var initialized = false;

    return {
        initialize: initialize,
        isInitialized: isInitialized
    }

    function intialize() {

        return $http.get(...)
                    .success(function(response) {
                            initialized=true;
                    });

    }

    function isInitialized() {
        return initialized;
    }
};

You should call $urlRouter.sync() after you have got user data. Also you might want to protect $stateChangeStart by using event.preventDefault() in case when user was not obtained.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论