Отложить Angular UI Router $stateChangeStart до получения ответа на авторизацию сервера

У меня есть приложение Angular, использующее UI-маршрутизатор, где я пытаюсь проверить токен пользователя, если он существует, когда приложение запускается. Я также проверяю, что у пользователя есть разрешение на доступ к сертификационным маршрутам. Проблема в том, что $stateChangeStart выполняется до того, как я получу ответ от конечной точки авторизации. Здесь некоторый код (coffeescript с js ниже) - это все в моем блоке run.

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

    # compare users access permissions with incoming route 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
)

Мой вопрос в том, как я могу запустить $stateChangeStart только после того, как ответ от конечной точки auth был возвращен. Это нужно только в первый раз. Каждое последующее изменение состояния может быть выполнено без попадания конечной точки auth.

Ответы

Ответ 1

Я бы создал функцию в вашей службе Auth, которая возвращает обещание. Позже, разрешите (разрешить) или отклоните (не authrized), который отложен. Затем используйте его в свойстве resolve определений маршрутов

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

Для поддержки последующих проверок вы можете сохранить обещание в сервисе. Это может выглядеть так:

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..            
    };



});

о, жаль, что кофе нет

Ответ 2

Вы должны позвонить $urlRouter.sync() после получения пользовательских данных. Также вы можете защитить $stateChangeStart, используя event.preventDefault(), если пользователь не был получен.

Ответ 3

Я думаю, что слишком поздно для ответа на этот вопрос, но я хочу прокомментировать здесь кого-то, кому может понадобиться небольшая идея.

Я пытался сделать почти то же самое в течение долгого времени, и я прекратил это делать. Я не думаю, что слушатель stateChangeStart является надежным в использовании. Я думаю, что лучший способ сделать то, что вы хотите, украсить $state.go с помощью службы $provide. Вот пример:

/* 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;
  });

При таком подходе вы можете делать все, что хотите, до $stateChangeStart. Надеюсь, это поможет.

Ответ 4

Я нашел очень хороший способ сделать эту работу должным образом и не полагаться на решение вообще в другом ответе здесь. Вот код:

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);
        });
    }
});

Тогда вы можете просто помнить, что ваши данные уже инициализированы в службе так, как вам нравится, например:

function dataService() {

    var initialized = false;

    return {
        initialize: initialize,
        isInitialized: isInitialized
    }

    function intialize() {

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

    }

    function isInitialized() {
        return initialized;
    }
};