使 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 计算的主要内容,如果未能解决你的问题,请参考以下文章

Angular 2+等待方法/可观察完成

“NeedAuthGuard”类错误地实现了“CanActivate”类。您的意思是扩展“CanActivate”并将其成员作为子类继承吗?

TS2416:“MyGuard”类型中的属性“canActivate”不可分配给基类型“CanActivate”中的相同属性

Angular 2 - 路由 - CanActivate 与 Observable 一起工作

使用来自 CanActivate 守卫的 store.dispatch

PassportJS、NestJS:AuthGuard('jwt') 的 canActivate 方法