在运行应用程序并切换到路由或状态之前解析$ http请求

时间:2013-11-01 05:06:08

标签: javascript angularjs angular-ui-router angular-routing

我编写了一个应用程序,我需要在应用程序运行之前检索当前登录用户的信息,然后再处理路由。我使用ui-router来支持多个/嵌套视图,并提供更丰富的有状态路由。

当用户登录时,他们可能会存储代表其身份验证令牌的cookie。我通过调用服务来包含该令牌以检索用户的信息,其中包括他们所属的组。然后在服务中设置生成的标识,在服务中可以检索它并在应用程序的其余部分中使用它。更重要的是,路由器将使用该身份确保他们登录并属于相应的组,然后再将其转换为请求的状态。

我的代码是这样的:

app
    .config(['$stateProvider', function($stateProvider) {

        // two states; one is the protected main content, the other is the sign-in screen

        $stateProvider
            .state('main', {
                url: '/',
                data: {
                    roles: ['Customer', 'Staff', 'Admin']
                },
                views: {} // omitted
            })
            .state('account.signin', {
                url: '/signin',
                views: {} // omitted
            });
    }])
    .run(['$rootScope', '$state', '$http', 'authority', 'principal', function($rootScope, $state, $http, authority, principal) {

        $rootScope.$on('$stateChangeStart', function (event, toState) { // listen for when trying to transition states...
            var isAuthenticated = principal.isAuthenticated(); // check if the user is logged in

            if (!toState.data.roles || toState.data.roles.length == 0) return; // short circuit if the state has no role restrictions

            if (!principal.isInAnyRole(toState.data.roles)) { // checks to see what roles the principal is a member of
                event.preventDefault(); // role check failed, so...
                if (isAuthenticated) $state.go('account.accessdenied'); // tell them they are accessing restricted feature
                else $state.go('account.signin'); // or they simply aren't logged in yet
            }
        });

        $http.get('/svc/account/identity') // now, looks up the current principal
            .success(function(data) {
                authority.authorize(data); // and then stores the principal in the service (which can be injected by requiring "principal" dependency, seen above)
            }); // this does its job, but I need it to finish before responding to any routes/states
    }]);

如果我登录,导航,退出等,这一切都按预期工作。问题是,如果我在登录时刷新或删除URL,我会被发送到登录屏幕,因为身份服务在状态发生变化之前,呼叫尚未完成。然而,在该调用完成之后,如果存在链接或某些内容(例如main状态),我可以按预期继续工作,所以我几乎就在那里。

我知道您可以让状态等待在转换之前解析参数,但我不确定如何继续。

3 个答案:

答案 0 :(得分:10)

好的,经过多次拔毛,这就是我想到的。

  1. 正如您所料,resolve是启动任何异步调用并确保在状态转换之前完成的适当位置。
  2. 您需要为确保resolve发生的所有状态创建抽象父状态,这样如果有人刷新浏览器,您的异步解析仍然会发生并且您的身份验证正常运行。您可以使用parent上的state属性创建另一个不会通过命名/点表示法继承的状态。这有助于防止您的州名变得无法管理。
  3. 虽然您可以向resolve注入所需的任何服务,但您无法访问其尝试转换到的州的toStatetoStateParams。但是,$stateChangeStart事件将在resolve解决之前发生。因此,您可以将toStatetoStateParams从事件参数复制到$rootScope,然后将$rootScope注入resolve函数。现在您可以访问它正试图转换到的状态和参数。
  4. 解决了资源后,您可以使用promises进行授权检查,如果失败,请使用$state.go()将其发送到登录页面,或者做您需要做的任何事情。当然,有一点需要注意。
  5. 在父状态下完成resolve后,ui-router将无法再次解析它。这意味着您的安全检查不会发生!哎呀!解决方案是进行两部分检查。在我们已经讨论过的resolve之后。第二次是$stateChangeStart事件。这里的关键是检查并查看资源是否已解决。如果是,请执行您在resolve中执行的相同安全检查,但事件是这样。如果资源已解决,则resolve中的签入将会将其取消。要解决这个问题,您需要在服务中管理资源,以便适当地管理状态。
  6. 其他一些错误。说明:

    • 不要试图将所有authz逻辑塞入$stateChangeStart。虽然您可以阻止事件并执行异步解析(在您准备好之前有效地停止更改),然后尝试并在您的promise成功处理程序中恢复状态更改,但是存在一些阻止其正常工作的问题。
    • 您无法更改当前状态的onEnter方法中的状态。

    This plunk是一个有效的例子。

答案 1 :(得分:1)

我们遇到了类似的问题。我们觉得我们需要制作构成HTTP的逻辑 调用可以访问处理响应的逻辑。他们是分开的 代码,所以服务是一种很好的方法。

我们通过将$http.get调用包装在一个服务中来解决这种分离问题 如果缓存是,则缓存响应并立即调用成功回调 已经人口稠密。 E.g。

app.service('authorizationService', ['authority', function (authority) {
    var requestData = undefined;

    return {
        get: function (successCallback) {
            if (typeof requestData !== 'undefined') {
                successCallback(requestData);
            }
            else {
                $http.get('/svc/account/identity').success(function (data) {
                    requestData = data;
                    successCallback(data);
                });
            }
        }
    };
}]);

这可以防止请求成功。然后,您可以在authorizationService.get()内拨打$stateChangeStart 安全处理。

如果在调用authorizationService.get()时请求已在进行中,则此方法容易受到竞争条件的影响。有可能引入一些XHR簿记来防止这种情况。

或者,您可以在HTTP请求发布后发布自定义事件 完成并注册该活动的订户 $stateChangeStart处理程序。您需要稍后取消注册该处理程序 但是,可能在$stateChangeEnd处理程序中。

在授权完成之前,这些都无法完成,因此您应该通知 他们需要通过显示加载视图来等待的用户。

此外,还有一些有关ui-router认证的有趣讨论 关于AngularJS Google Group的How to Filter Routes?

答案 2 :(得分:0)

我认为处理这种情况的一种更简洁的方法是将$urlRouterProvider. deferIntercept()$urlRouter.listen()一起使用,以便在从服务器检索到某些数据之前停止uiRouter。

有关详细信息,请参阅this answerdocs