简体   繁体   English

使CanActivate等待Subject计算

[英]Make CanActivate wait for Subject to compute

I am trying to implement an authentication guard in my Angular + Firebase application. 我正在尝试在我的Angular + Firebase应用程序中实现身份验证防护。

I am using a combination of Firebase Authentication (FA) and Firebase Cloud Firestore (FCF) to save users. 我正在使用Firebase身份验证(FA)和Firebase Cloud Firestore(FCF)的组合来保存用户。 Therefore, just FA User isn't enough, I need to find the same user in FCF to consider the user logged-in. 因此,仅仅FA用户还不够,我需要在FCF中找到相同的用户才能考虑用户登录。

During registration, I am saving the users in FCF under the id same as the uid of that user in FA. 在注册期间,我在FC中将用户保存在与FA中该用户的uid相同的id下。

In my authentication.service : 在我的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);
                  }
                }
              );
            }
          }
        ); 
      }   
    }

According to my understanding, when the service is initialised, this piece of code will start listening to changes in FA user, following which I will check the FCF user and update authenticatedUserSubject . 根据我的理解,当服务初始化时,这段代码将开始收听FA用户的更改,然后我将检查FCF用户并更新authenticatedUserSubject

In my authenticated.guard : 在我的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;
              }
            }
          )
        );
      }

Problems: 问题:

  • The guard does not receive any value from the subject 警卫不会从主题中获得任何价值
  • If I use BehaviourSubject instead of Subject with an initial value of null , refreshing the page will emit null as the default value, throwing the user to /sign-in 如果我使用BehaviourSubject而不是Subject,初始值为null ,刷新页面将发出null作为默认值,将用户抛给/sign-in

Goals: 目标:

  • On refresh / load of application, the authenticatedUserSubject should be calculated before the route guard runs 在刷新/加载应用程序时,应在路由防护运行之前计算authenticatedUserSubject
  • On logout, the guard should be aware of the state change 注销时,警卫应该知道状态变化

Also, if there is a better way to implement this, please let me know! 另外,如果有更好的方法来实现这一点,请告诉我!

I guess you can use BehaviorSubject with initial value of null as you said but all you have to do in your guard is to use takeWhile in the following way. 我猜你可以使用BehaviorSubject,初始值为null,但是你所要做的就是以下面的方式使用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)
    );

  }

As you know method requires boolean so you can map it before takeWhile in pipe. 如您所知,方法需要布尔值,因此您可以在管道中的takeWhile之前映射它。

Hope this works for you cause I had the same problem and it worked. 希望这对你有用,因为我有同样的问题而且有效。

You should use BehaviourSubject instead of Subject . 您应该使用BehaviourSubject而不是Subject The subject does not emit the last emitted value to the subscriber. 主体不向订户发出最后发射的值。 Subscriber of Subject will get value only when Subject.next() after the subscription. 主题订阅者只有在订阅后的Subject.next()时才会获得价值。 This is not the case with BehaviourSubject . BehaviourSubject不是这种情况。 Subscriber will always get the last emitted value on subscription. 订阅者将始终获得订阅的最后一次发布值。 With this let's try to improve your code a bit and see if your problem solves: 有了这个让我们尝试改进你的代码,看看你的问题是否解决:

Change your service like this- 像这样改变你的服务 -

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();       
    }   
  }

Having this will avoid chaining of subscribe() 这样可以避免subscribe()链接

Now let's rewrite canActivate like this: 现在让我们像这样重写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)
                   );                   
      }

Let us know if it works. 如果有效,请告诉我们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM