2
votes

For context I've got two guards on an angular route but I can't have them both on the route canActivate due to side effects in one and the fact that all guards get checked on a route regardless.

So to workaround I have one guard that calls the others canActivate in a certain case:

canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<boolean> | Promise<boolean> | boolean {
        return this.service.state$.pipe(
            filter((s) => s.updatedFromServer),
            switchMap((s) => {
                if (/* a condition that means this guard is satisfied */) {
                    return this.otherGuard.canActivate(next, state);
                } else {
                    return false;
                }
            })
        );

The other guard's canActivate return type is also Observable<boolean> | Promise<boolean> | boolean and similarly can return an actual boolean or Observable.

The TS error complaint is now:

Type 'false | Observable<boolean>' is not assignable to type 'ObservableInput<any>'.
Type 'false' is not assignable to type 'ObservableInput<any>'

If the other guard returns just an Observable and the switchmap return here is also changed to of(false) then it seems ok, but I would prefer to retain the canActivate signatures where a resolved boolean can be returned - I just don't know how to do that within the context of a rxjs map.

1
What is the reason why you cannot return this of(false)? - Bargros
switchMap operator always returns an observable. So you would need to return of(false). I did not quite understand why you couldn't return an observable of boolean? Could you elaborate a little please. - Alvin Saldanha
I can reply of(false) in this guard, but the other guard is used elsewhere independently and I didn't want to necessarily change it. I can if there isn't a better way - just thought there might be an rxjs operator that would allow me to merge a mix of boolean and Observable<booleans>. But it sounds like there isn't - Robin Southgate
By returning of(false) you're not breaking the canActivate signature. The xxxMap operators do require you to return an observable. By not returning an observable you're breaking the switchMap contract itself. Can't you include the condition inside the service itself so that the observable could return true/false? - LookForAngular

1 Answers

0
votes

You can check if the result is an Observable or Promise with isObservable and isPromise. If not wrap the value with of.

import { isObservable } from 'rxjs';
import { isPromise } from 'rxjs/internal-compatibility';

return this.service.state$.pipe(
  filter((s) => s.updatedFromServer),
  switchMap((s) => {
    let result: any;
    if (/* a condition that means this guard is satisfied */) {
      result = this.otherGuard.canActivate(next, state);
    } else {
      result = false;
    }
    return isObservable(result) || isPromise(result) ? result : of(result)
  })
);