我在角度2应用中遇到了一个棘手的异步问题问题。
当我在用户的浏览器中重新加载/引导应用程序时,我基本上尝试从后端重新水化/重新加载信息(想想F5 /刷新)。问题是在后端异步方法返回结果之前,会调用路由器保护并阻塞...
我从根组件的ngOnInit
方法重新加载信息,如下所示:
来自root组件:
ngOnInit() {
//reloadPersonalInfo returns an Observable
this.sessionService.reloadPersonalInfo()
.subscribe();
}
来自sessionService
:
reloadPersonalInfo() {
//FIRST: the app execution flow gets here
let someCondition: boolean = JSON.parse(localStorage.getItem('someCondition'));
if (someCondition) {
return this.userAccountService.retrieveCurrentUserAccount()
.switchMap(currentUserAccount => {
//THIRD: and finally, the execution flow will get there and set the authenticated state to true (unfortunately too late)...
this.store.dispatch({type: SET_AUTHENTICATED});
this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
return Observable.of('');
});
}
return Observable.empty();
}
问题是我有一个路由器CanActivate
保护,如下所示:
canActivate() {
//SECOND: then the execution flow get here and because reloadPersonalInfo has not completed yet, isAuthenticated will return false and the guard will block and redirect to '/signin'
const isAuthenticated = this.sessionService.isAuthenticated();
if (!isAuthenticated) {
this.router.navigate(['/signin']);
}
return isAuthenticated;
}
来自 所以回顾一下: 有人可以帮忙吗? 编辑1 :让我强调重要的 编辑2 :我将条件从 编辑3 :我已将 来自 不幸的是,没有任何区别...... 有人可以就如何排除这种异步性问题提出建议吗?sessionService
的 isAuthenticated(): boolean {
let isAuthenticated = false;
this.store.select(s => s.authenticated)
.subscribe(authenticated => isAuthenticated = authenticated);
return isAuthenticated;
}
reloadPersonalInfo
上的sessionService
方法由根组件ngOnInit
调用。执行流程进入此方法。authenticated
状态为false
(因为reloadPersonalInfo
尚未完成,因此未设置{{1}状态为authenticated
。true
完成太晚并将reloadPersonalInfo
状态设置为authenticated
(但路由器警卫已被阻止)。true
状态是商店中的状态;它由此行设置:authenticated
。this.store.dispatch({type: SET_AUTHENTICATED});
更改为authenticated
,以减少混淆。以前,还有另一个名为someCondition
... authenticated
方法返回类型更改为isAuthenticated()
而不是Observable<boolean>
(遵循Martin的建议)并调整{{1方法如下:boolean
canActivate
: canActivate() {
return this.sessionService.isAuthenticated().map(isAuthenticated => {
if (isAuthenticated) {
return true;
}
this.router.navigate(['/signin']);
return false;
});
}
答案 0 :(得分:2)
应该有两种可能的方法来解决这个问题:
最快的方法是将你的isAuthenticated
区分为2个而不是3个状态,这样你就可以将另外一条信息编码到状态中:在引导时(当服务器没有响应时)已收到),客户无法确定其凭证/代币是否有效,因此状态应该正确地是&#34;未知&#34;。
首先,您必须将商店中authenticated
的初始状态更改为null
(您也可以选择undefined
,甚至根据个人喜好使用数字)。然后你只需要为你的后卫添加一个.filter
,这样几乎可以让守卫保持不动状态&#34;:
canActivate() {
return this.sessionService.isAuthenticated()
.filter(isAuthenticated => isAuthenticated !== null) // this will cause the guard to halt until the isAuthenticated-question/request has been resolved
.do(isAuth => (if (!isAuth) {this.router.navigate(['/signin'])}));
}
第二种解决方案非常相似,但不是将第三种状态编码为authenticated
,而是在商店中添加一个名为authRequestRunning
的新标记,设置为{{1正在进行身份验证请求,并在完成后设置为true
。然后你的警卫看起来会略有不同:
false
使用解决方案#2,您可能会有更多代码。 并且在 canActivate() {
return this.sessionService.isAuthenticated()
.switchMap(isAuth => this.sessionService.isAuthRequestRunning()
.filter(running => !running) // don't emit any data, while the request is running
.mapTo(isAuth);
)
.do(isAuth => (if (!isAuth) {this.router.navigate(['/signin'])}));
}
- 状态更新之前,您必须小心authRequestRunning
设置为false
优先。
编辑:我已经在解决方案#2中编辑了代码,因此设置运行状态和身份验证状态的顺序不不再重要。< / p>
我之所以使用解决方案#2,是因为在大多数情况下,这样的状态标志已经存在,并且用于显示加载指示器或类似的东西。
答案 1 :(得分:0)
为什么在拨打retrieveCurrentUserAccount
电话之前未设置身份验证?根据{{1}}
localStorage
<强>更新强>
尝试以下,
if (isAuthenticated) {
// set before you give a async call.
this.store.dispatch({type: SET_AUTHENTICATED});
return this.userAccountService.retrieveCurrentUserAccount()
.switchMap(currentUserAccount => {
//THIRD: and finally, the execution flow will get there and set the authenticated state to true (unfortunately too late)...
this.store.dispatch({type: SET_CURRENT_USER_ACCOUNT, payload: currentUserAccount});
return Observable.of('');
});
}
以下是Plunker!!
希望这会有所帮助!!
答案 2 :(得分:0)
canActivate
本身可以返回一个Observable。
不是在canActivate
中返回布尔结果,而是返回isAuthenticated
Observable。