$ state.go不在诺言工作

时间:2015-03-18 00:00:29

标签: javascript angularjs promise angular-promise angular-http

我正在尝试实施基本安全检查,以阻止用户访问某些状态,具体取决于他们的权限集:

'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(),它就有效。但是状态改变分为两步。首先,它将始终转到原始页面,然后在转换到“被阻止的”页面后很快就会出现。页面(如果被阻止)。我收集这是因为它从$ on成功返回一次并更新状态,然后在从promise返回时再次更新它。

如果承诺是你如何看待它,状态将不会改变。一切似乎都有效,但页面不会更新视图。我不确定为什么会这样。出于某种原因,如果未阻止事件,则稍后的状态更改将起作用。但是,如果它被阻止并且从返回的promise中调用$ 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'从自然射击。手动执行它可以恢复预期的行为。

3 个答案:

答案 0 :(得分:4)

这是一个众所周知的问题。 一般工作的解决方法是从'$ 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

答案 1 :(得分:1)

我认为弗拉基米尔·古罗维奇"广播"解决方案是要走的路,特别是因为他表示这是一个既定的解决方法。

但是我想知道一个解决方案,其中权限被缓存然后查找同步,也可能是可行的。

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的任何更改都需要清除并重新加载缓存。

如果没有研究/测试,我无法说明这些问题是否重要。

答案 2 :(得分:0)

为什么不使用解析功能来检查用户是否可以访问您的路线? 像这样:

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