$state.go не работает из обещания

Я пытаюсь реализовать базовую проверку безопасности, чтобы заблокировать пользователям доступ к определенным состояниям в зависимости от их набора разрешений:

'use strict';

var autoExecApp = angular.module("myApp", ['ui.router']);

autoExecApp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider){
    $stateProvider
        .state('index', {
            url: '/index',
            templateUrl: 'partials/index.html'
        })
        .state('permission', {
            url: '/permission',
            templateUrl: 'partials/permissions.html'
        });
}]);

autoExecApp.run(['$rootScope', '$state', '$userRoles', function($rootScope, $state, $userRoles) {

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

        event.preventDefault(); // prevent page from being loaded anyway
        $userRoles.hasPermissions(toState.name).then(function(data) {
            var result = data === "true";
            if (!result) {
                $state.go('permission');
            }
            else {
                $state.go(toState.name, toParams ,{notify:false});
            }
        });
    });
}]);

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

Если я перемещу event.preventDefault() внутри обещания, это сработает. НО состояние меняется за 2 шага. Во-первых, он всегда будет переходить на исходную страницу, а затем вскоре после перехода на «заблокированную» страницу (если она была заблокирована). Я так понимаю, это потому, что он успешно возвращается один раз из $on и обновляет состояние, а затем снова обновляет его, когда возвращается из обещания.

Однако если обещание такое, каким вы его видите, состояние не изменится. Все работает, но страница не обновляется. Я не уверен, почему это так. По какой-то причине, если событие не предотвратить, сработает более позднее изменение состояния. Однако, если это предотвращено и $state.go вызывается из возвращенного промиса, кажется, что уже слишком поздно обновлять $state.

РЕДАКТИРОВАТЬ: я успешно использовал широковещательное решение, и вот как оно выглядело:

event.preventDefault();
$state.go(toState.name, toParams, {notify: false}).then(function() {
     // line 907 state.js
     $rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
});

По-видимому, из-за того, что это обещание, функция preventDefault(); подавляет естественное срабатывание этого '$stateChangeSuccess'. Выполнение этого вручную восстанавливает ожидаемое поведение.


person Alex    schedule 18.03.2015    source источник


Ответы (3)


Это известная проблема. Обходной путь, который обычно работает, заключается в передаче пользовательского события из обработчика $stateChangeStart, а затем в обработчике этого пользовательского события.

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

    event.preventDefault(); // prevent page from being loaded anyway
    $userRoles.hasPermissions(toState.name).then(function(data) {
        var result = data === "true";
        if (!result) {

            $rootScope.$broadcast('goToPermission');
        }
        else {
            $rootScope.$broadcast('goToState', toState.name);
        }
    });
});

заимствовано из: https://github.com/angular-ui/ui-router/issues/178#issuecomment-49156829

person Vlad Gurovich    schedule 18.03.2015
comment
Это действительно содержало ответ в прикрепленной ссылке, но что сработало, так это привязка трансляции события «stateChangeSuccess» к методу state.go. Я не добился успеха, просто поместив переход в метод «$ on» - person Alex; 30.03.2015

Я думаю, что "широковещательное" решение Владимира Гуровича - это то, что нужно, тем более что он указывает, что это установленный обходной путь.

Но мне интересно, может ли быть жизнеспособным решение, в котором разрешения кэшируются, а затем синхронно просматриваются.

autoExecApp.run(['$rootScope', '$state', '$userRoles', function($rootScope, $state, $userRoles) {
    // Create a permissions cache, seeded with 
    // permission to go to the 'permission' state.
    var permissionCache = {
        'permission': true
    };

    // Create a researched list of all possible state names.
    var stateNames = ['stateA', 'stateB', 'stateC'];

    // Now load the cache asynchronously with 
    // `true` for each permission granted.
    stateNames.forEach(function(state) {
        $userRoles.hasPermissions(state).then(function(data) {
            if(data === "true") {
                permissionCache[state] = true;
            }
        });
    });

    // And finally, establish a $stateChangeStart handler 
    // that looks up permissions **synchronously**, 
    // in the permissionCache.
    $rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
        if (!permissionCache[toState.name]) {
            event.preventDefault(); // prevent page from being loaded
            $state.go('permission');
        }
    });
}]);

Конечно, это решение будет иметь свои проблемы:

  • опирается на хорошее исследование всех возможных имен состояний.
  • любая попытка изменения состояния до загрузки кеша будет отклонена.
  • любое изменение userRoles в жизни страницы потребует очистки и перезагрузки кеша.

Без исследования/тестирования я не могу сказать, существенны ли эти проблемы или нет.

person Roamer-1888    schedule 18.03.2015

Почему бы вам не использовать функцию разрешения, чтобы проверить, может ли пользователь получить доступ к вашему маршруту?
Что-то вроде этого:

autoExecApp.config(['$stateProvider', '$urlRouterProvider', function (
    $stateProvider, $urlRouterProvider) {
    $stateProvider
        .state('index', {
            url: '/index',
            templateUrl: 'partials/index.html'
        })
        .state('permission', {
            url: '/permission',
            templateUrl: 'partials/permissions.html'
        })
    .state('restricted-route', {
      url: '/restricted',
      templateUrl: 'partials/restricted.html',
      resolve: {
        authenticated: function ($q) {
          var deferred = $q.defer();
          $userRoles.hasPermissions(this)
            .then(function (data) {
              var result = data === "true";
              if (result) {
                deferred.resolve();
              } else {
                deferred.reject('unauthorized');
              }
            });
          return deferred.promise;
        }
      }
    });
}]);

$rootScope.$on('$stateChangeError',
  function (event, toState, toParams, fromState, fromParams, rejection) {

    if (rejection.error === 'unauthorized') {
      $state.go('permission');
    }
  }
}
person pasine    schedule 18.03.2015