canActivate
.我已经阅读了
another SO question.使用以下代码,我的canActivate方法仅在应用程序启动时工作一次,但是当isLoggedIn observable触发新值时,永远不会再打印hello.
canActivate(): Observable<boolean> { return this.authService.isLoggedIn().map(isLoggedIn => { console.log('hello'); if (!isLoggedIn) { this.router.navigate(['/login']); } return isLoggedIn; }).first(); }
或者这个版本不能正常工作:
canActivate(): Observable<boolean> { return this.authService.isLoggedIn().map(isLoggedIn => { console.log('hello'); if (isLoggedIn) { this.router.navigate(['/']); } return !isLoggedIn; }); }
但是,它适用于此代码:
canActivate(): Observable<boolean> { return Observable.create(obs => { this.authService.isLoggedIn().map(isLoggedIn => { console.log('hello'); if (isLoggedIn) { this.router.navigate(['/']); } return !isLoggedIn; }).subscribe(isLoggedIn => obs.next(isLoggedIn)); }); }
我在第一段代码中做错了什么?
编辑:这是isLoggedIn实现
@LocalStorage(AuthService.JWT_TOKEN_KEY) private readonly token: string; private tokenStream: Subject<string>; public isLoggedIn(): Observable<boolean> { if (!this.tokenStream) { this.tokenStream = new BehaviorSubject(this.token); this.storage.observe(AuthService.JWT_TOKEN_KEY) .subscribe(token => this.tokenStream.next(token)); } return this.tokenStream.map(token => { return token != null }); }
使用ngx-webstorage
.和RxJS BehaviorSubject
.
解决方法
这是我从AngularJs的承诺转换为Angular的Observable模式时遇到的困难之一.你看到承诺是拉动通知,而观察者是推送通知.因此,您必须重新考虑您的AuthService,以便它使用推送模式.即使在我编写工作Observables时,我一直在考虑拉动.在拉动方面,我无法停止思考.
使用承诺模式更容易.创建AuthService时,它将创建一个解析为“未登录”的promise,或者它将创建一个“还原已记录状态”的异步保证.然后,您可以使用名为isLoggedIn()的方法来返回该promise.这使您可以轻松处理显示用户数据和收到用户数据之间的延迟.
AuthService作为推送服务
现在,我们切换到Observables,动词“is”需要更改为“when”.进行这一小改动有助于您重新思考事情的发展方向.因此,我们将“isLoggedIn”重命名为“whenLoggedIn()”,这将是一个在用户进行身份验证时发出数据的Observable.
class AuthService { private logIns: Subject = new Subject<UserData>(); public setUser(user: UserData) { this.logIns.next(user); } public whenLoggedIn(): Observable<UserData> { return this.logIns; } } // example AuthService.whenLoggedIn().subscribe(console.log); AuthService.setUser(new UserData());
当用户传递给setUser时,它会发出以订阅新用户的身份验证.
以上方法的问题
以上介绍了需要修复的几个问题.
>订阅whenLoggedIn将永远收听新用户.拉流永远不会完成.
>没有“现状”的概念.推送给订阅者后,之前的setUser会丢失.
>它仅告知您何时对用户进行身份验证.如果没有当前用户则不会.
我们可以通过从Subject切换到BehaviorSubject来解决一些问题.
class AuthService { private logIns: Subject = new BehaviorSubject<UserData>(null); public setUser(user: UserData) { this.logIns.next(user); } public whenLoggedIn(): Observable<UserData> { return this.logIns; } } // example AuthService.whenLoggedIn().first().subscribe(console.log); AuthService.setUser(new UserData());
这更接近我们想要的.
变化
> BehaviorSubject将始终为每个新订阅发出最后一个值.
> whenLoggedIn().first()被添加到订阅并在收到第一个值后自动取消订阅.如果我们没有使用BehaviorSubject,那么会阻塞,直到有人调用setUser,这可能永远不会发生.
BehaviorSubject的问题
BehaviorSubject不适用于AuthService,我将在此处演示此示例代码.
class AuthService { private logIns: Subject = new BehaviorSubject<UserData>(null); public constructor(userSessionToken:string,tokenService: TokenService) { if(userSessionToken) { tokenService.create(userSessionToken).subscribe((user:UserData) => { this.logIns.next(user); }); } } public setUser(user: UserData) { this.logIns.next(user); } public whenLoggedIn(): Observable<UserData> { return this.logIns; } }
// example let auth = new AuthService("my_token",tokenService); auth.whenLoggedIn().first().subscribe(console.log);
上面创建了一个带有令牌的新AuthService来恢复用户会话,但是当它运行时,控制台只打印“null”.
发生这种情况是因为使用初始值null创建了BehaviorSubject,并且在HTTP调用完成后将恢复用户会话的操作.在会话恢复之前,AuthService将继续发出null,但是当您想要使用路由激活器时,这是一个问题.
ReplaySubject更好
我们想要记住当前用户,但在我们知道是否有用户之前不会发出任何内容. ReplaySubject是这个问题的答案.
这是你如何使用它.
class AuthService { private logIns: Subject<UserData> = new ReplaySubject(1); public constructor(userSessionToken:string,tokenService: TokenService) { if(userSessionToken) { tokenService.create(userSessionToken).subscribe((user:UserData) => { this.logIns.next(user); },()=> { this.logIns.next(null); console.error('could not restore session'); }); } else { this.logIns.next(null); } } public setUser(user: UserData) { this.logIns.next(user); } public whenLoggedIn(): Observable<UserData> { return this.logIns; } } // example let auth = new AuthService("my_token",tokenService); auth.whenLoggedIn().first().subscribe(console.log);
上述内容不会等到whenLoggedIn发出第一个值.它将获得第一个值并取消订阅.
ReplaySubject可以正常工作,因为它可以记住1个项目或者什么都不发出.这不是重要的部分.当我们在canActivate中使用AuthService时,我们希望等到用户状态已知.
CanActivate示例
现在,这使得编写重定向到登录屏幕或允许路由更改的用户警卫变得更加容易.
class UserGuard implements CanActivate { public constructor(private auth: AuthService,private router: Router) { } public canActivate(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<boolean> { return this.auth.whenLoggedIn() .first() .do((user:UserData) => { if(user === null) { this.router.navigate('/login'); } }) .map((user:UserData) => !!user); }
如果存在用户会话,则将产生Observable为true或false.它还会阻止路由器更改,直到该状态已知(即我们是否从服务器获取数据?).