'Angular canActivate guard - conditional based on a selector
I have a CanActivate guard service that has been working fine, until a new requirement came along. The original code was something like this:
canActivate(
route: ActivatedRoutesnapshot,
state: RouterStateSnapshot
): boolean | UrlTree | Observable<boolean> | Promise<boolean | UrlTree> {
if (!this.isUserLoggedIn()) {
... force login, return false
} else if (this.shouldDoMoreChecks()) {
return doMoreChecks(); // returns boolean | UrlTree | Promise<boolean | UrlTree>
} else {
return this.authGuardService.canActivate(); // returns boolean | Promise<boolean> | Observable<boolean>
}
Now we have a new requirement that we need to subscribe to a selector and depending on the value returned, add additional checks in the second branch. Since this is (obviously) asynchronous, my idea was that I should put the subscription in the second branch like so:
if (!this.isUserLoggedIn()) {
... force login, return false
} else {
return this.store$.select(mySelector).pipe(
take(1),
map(response => {
if (this.shouldDoMoreChecks(response)) {
return doMoreChecks(); // returns boolean | UrlTree | Promise<boolean | UrlTree>
} else {
return this.authGuardService.canActivate(); // returns boolean | Promise<boolean> | Observable<boolean>
}
}
}); // returns Observable<boolean | UrlTree | Promise<boolean | UrlTree>>
}
I'm at a loss as to how to "unwrap" the Observable returned from the map() function so the return isn't
Observable<boolean | UrlTree | Promise<boolean | UrlTree>>
but is instead
boolean | UrlTree | Promise<boolean | UrlTree>
I'm sure there's something blindingly obvious, but I'm not seeing it.
EDIT So, I had tried using switchMap before but had given up because of errors. Now I realize that I just needed to fix the return types of the various functions. I've implemented switchMap and am testing, so far so good. One thing I ended up having to do was
return of(Boolean(this.guardService.canActivate(route, state).valueOf()));
because it could possibly return boolean, which switchMap did not approve of. It seems inelegant, but I'm not sure there's a different way to handle it?
Solution 1:[1]
I'm at a loss as to how to "unwrap" the Observable returned from the map() function so the return isn't
To unwrap the value, you need to use a "Higher Order Mapping Operator", such as switchMap.
So, just change your map to switchMap:
canActivate(): Observable<boolean> {
return !this.isUserLoggedIn()
? of(false) // ... force login, return false
: this.store$.select(mySelector).pipe(
take(1),
switchMap(response => this.shouldDoMoreChecks(response)
? doMoreChecks()
: this.authGuardService.canActivate()
// if .canActivate() returns plain boolean, wrap with of()
)
);
}
Note, the function passed to switchMap, should return an observable (or promise), so you may need to wrap your authGuardService.canActivate() with of to make it into observable:
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | BizzyBob |
