[英]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: 问题:
null
, refreshing the page will emit null
as the default value, throwing the user to /sign-in
如果我使用BehaviourSubject而不是Subject,初始值为null
,刷新页面将发出null
作为默认值,将用户抛给/sign-in
Goals: 目标:
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.