'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