使 CanActivate 等待 Subject 计算
Posted
技术标签:
【中文标题】使 CanActivate 等待 Subject 计算【英文标题】:Make CanActivate wait for Subject to compute 【发布时间】:2019-11-03 06:08:24 【问题描述】:我正在尝试在我的 Angular + Firebase 应用程序中实现身份验证保护。
我正在使用 Firebase 身份验证 (FA) 和 Firebase Cloud Firestore (FCF) 的组合来保存用户。所以仅仅FA用户是不够的,我需要在FCF中找到同一个用户来考虑用户登录。
在注册期间,我将 FCF 中的用户保存在 id
下,与 FA 中该用户的 uid
相同。
在我的authentication.service
:
export class AuthenticationService
public authenticatedUserSubject: Subject<null | User> = new Subject <null | User>();
public constructor(
private readonly angularFireAuth: AngularFireAuth,
private readonly angularFirestore: AngularFirestore
)
this.angularFireAuth.authState.subscribe(
(userInfo: null | UserInfo) =>
if (userInfo === null)
this.authenticatedUserSubject.next(null);
else
this.angularFirestore.collection<User>('users').doc<User>(userInfo.uid).valueChanges().pipe(take(1)).subscribe(
(user: undefined | User): void =>
if (typeof user === 'undefined')
this.authenticatedUserSubject.next(null);
else
this.authenticatedUserSubject.next(user);
);
);
据我了解,当服务初始化时,这段代码会开始监听FA用户的变化,然后我会检查FCF用户并更新authenticatedUserSubject
。
在我的authenticated.guard
:
public canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot): Observable<boolean>
return this.authenticationService.authenticatedUserSubject.pipe(take(1)).pipe(
map(
(user: null | User): boolean =>
if (user === null)
this.router.navigateByUrl('/sign-in');
return false;
else
return true;
)
);
问题:
守卫没有从主体接收任何值 如果我使用 BehaviourSubject 而不是 Subject 初始值为null
,刷新页面将发出null
作为默认值,将用户扔到/sign-in
目标:
在刷新/加载应用程序时,应在路由保护运行之前计算 authenticatedUserSubject 退出时,守卫应该知道状态变化另外,如果有更好的实现方法,请告诉我!
【问题讨论】:
您是否有一个堆栈闪电战来实现您想要实现的目标? 【参考方案1】:我猜你可以使用初始值为 null 的 BehaviorSubject,但你所要做的就是按照以下方式使用 takeWhile
。
public canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot): Observable<boolean>
return this.authenticationService.authenticatedUserSubject.pipe(
tap((user: User) =>
if(!user)
this.router.navigate(['/sign-up']);
),
takeWhile((data) => data === null, true)
);
如您所知,方法需要布尔值,因此您可以在管道中的 takeWhile
之前映射它。
希望这对你有用,因为我遇到了同样的问题并且它有效。
【讨论】:
【参考方案2】:您应该使用BehaviourSubject
而不是Subject
。主题不会向订阅者发出最后发出的值。 Subject 的订阅者只有在订阅后的 Subject.next() 时才能获得价值。 BehaviourSubject
不是这种情况。订阅者将始终获得订阅时最后发出的值。有了这个,让我们试着改进一下你的代码,看看你的问题是否解决了:
像这样改变你的服务-
export class AuthenticationService
public authenticatedUserSubject: BehaviorSubject<User> = new BehaviorSubject<User>(null);
public constructor(
private readonly angularFireAuth: AngularFireAuth,
private readonly angularFirestore: AngularFirestore
)
combineLatest(this.angularFireAuth.authState,
this.angularFirestore.collection<User>('users').doc<User>(userInfo.uid).valueChanges()
)
.pipe(
tap(([userFromFireAuth, userFromFirestore]) =>
if (!userFromFireAuth)
this.authenticatedUserSubject.next(null);
else
if (typeof userFromFireAuth === 'undefined')
this.authenticatedUserSubject.next(null);
else
this.authenticatedUserSubject.next(userFromFireAuth);
)
).subscribe();
这样可以避免subscribe()
的链接
现在让我们像这样重写 canActivate:
public canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot, routerStateSnapshot: RouterStateSnapshot): Observable<boolean>
return this.authenticationService.authenticatedUserSubject
.pipe(
//this will wait until user becomes NOT NULL
skipWhile(user => !user),
tap(user =>
if (!user)
//this will be useful when user logged out and trying to reach a page which requires authentication
//NOTICE - When page is NOT refreshed
this.router.navigate(['/sign-up']);
),
take(1),
map(() => true)
);
让我们知道它是否有效。
【讨论】:
以上是关于使 CanActivate 等待 Subject 计算的主要内容,如果未能解决你的问题,请参考以下文章
“NeedAuthGuard”类错误地实现了“CanActivate”类。您的意思是扩展“CanActivate”并将其成员作为子类继承吗?
TS2416:“MyGuard”类型中的属性“canActivate”不可分配给基类型“CanActivate”中的相同属性
Angular 2 - 路由 - CanActivate 与 Observable 一起工作