[英]Resolve $http request before running app and switching to a route or state
我编写了一个应用程序,我需要在应用程序运行之前检索当前登录用户的信息,然后再处理路由。 我使用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
状态),我可以按预期继续工作,所以我几乎就在那里。
我知道你可以在转换之前让状态等待解析参数,但我不知道如何继续。
好吧,经过多次拔毛,这就是我想到的。
resolve
是启动任何异步调用并确保它们在状态转换之前完成的适当位置。 resolve
发生的所有状态创建抽象父状态,这样如果有人刷新浏览器,您的异步解析仍然发生并且您的身份验证正常工作。 您可以在state
上使用parent
属性来创建另一个状态,否则该状态将不会被命名/点表示法继承。 这有助于防止您的州名变得无法管理。 resolve
注入所需的任何服务,但您无法访问它尝试转换到的状态的toState
或toStateParams
。 但是, $stateChangeStart
事件将在resolve
之前发生。 因此,您可以将事件args中的toState
和toStateParams
复制到$rootScope
,并将$rootScope
注入resolve
函数。 现在您可以访问它尝试转换到的状态和参数。 $state.go()
将它们发送到登录页面,或者做任何你需要做的事情。 当然,有一点需要注意。 resolve
,ui-router将不再解析它。 这意味着您的安全检查不会发生! 哎呀! 解决方案是进行两部分检查。 一旦resolve
,我们已经讨论过了。 第二次是在$stateChangeStart
事件中。 这里的关键是检查并查看资源是否已解决。 如果是这样,你在做同样的安全检查resolve
,但在事件。 如果资源没有得到解决,那么check in resolve
将会resolve
它。 要实现此目的,您需要在服务中管理资源,以便适当地管理状态。 其他一些misc。 笔记:
$stateChangeStart
。 虽然您可以阻止事件并执行异步解析(在您准备好之前有效地停止更改),然后尝试在promise承诺成功处理程序中恢复状态更改,但是存在一些阻止其正常工作的问题。 onEnter
方法中的状态。 这个插件是一个工作的例子。
我们遇到了类似的问题。 我们觉得我们需要制作使HTTP调用可以访问处理响应的逻辑的逻辑。 它们在代码中是分开的,因此服务是一种很好的方法。
我们通过将$http.get
调用包装在缓存响应的服务中来解决该分离,并在已经填充缓存时立即调用成功回调。 例如
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);
});
}
}
};
}]);
这可以防止请求成功。 然后,您可以安全地在$stateChangeStart
处理程序中调用authorizationService.get()
。
如果在调用authorizationService.get()
时请求已在进行中,则此方法容易受到竞争条件的影响。 有可能引入一些XHR簿记来防止这种情况发生。
或者,您可以在HTTP请求完成后发布自定义事件,并在$stateChangeStart
处理程序中注册该事件的订阅者。 稍后您可能需要取消注册该处理程序,可能在$stateChangeEnd
处理程序中。
在授权完成之前,这些都无法完成,因此您应该通过显示加载视图通知用户他们需要等待。
另外,在如何过滤路由的过程中 ,有一些关于ui-router
认证的有趣讨论? 在AngularJS Google Group上。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.