Search code examples
angulartypescriptangular-route-guards

Do Angular routes subscribe to the observable a RouteGuards returns?


I'm a little confused by how Angular routes treat the observables that a guard returns. Take the following route guard:

export class AuthenticationGuard implements CanActivate {

  constructor(private sessionQuery: SessionQuery, private router: Router){}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

    const loginPage = this.router.parseUrl('/login')

    return this.sessionQuery.loggedIn$.pipe(
      map((loggedIn: boolean): boolean | UrlTree => {
        console.log('loggedIn emitted: ', loggedIn)
        return loggedIn ? true : loginPage
      })
    )
  }
}

The guard returns an observable that maps this.sessionQuery.loggedIn$ to either true if the value is true or loginPage if false.

However, I would expect that the route guard would subscribe to the observable: this.sessionQuery.loggedIn$ observable so that when it re-emits, the logic in the route-guard is re-evaluated.

However testing appears to show that it doesn't. If I subscribe to this observable elsewhere in the app I can see it re-emit but the console.log call I've put in the code above doesn't fire.

I would like the route guard to trigger an automatic logout when the loggedIn$ observable emits false.

If the route isn't subscribing the the guard then what's the point in the guard returning an observable in the first place? I feel like there's something I'm missing here...


Solution

  • You'll have to implement the logic for "route to login when sessionQuery.loggedIn$ emits false somewhere else, maybe in a service that is eagerly injected in its parent module (to make sure it is instantiated).

    To check what Angular is doing with the returned Observable, you may return the following. My guess is, that Angular gets the first emitted value and unsubsribes.

    return new Observable<boolean | UrlTree>((subscriber) => {
        const subscription = this.sessionQuery.loggedIn$.pipe(
          map((loggedIn: boolean): boolean | UrlTree => {
            console.log('loggedIn emitted: ', loggedIn)
            return loggedIn ? true : loginPage
          })
        ).subscribe(subscriber);
    
        return () => {
            // called when no subscriber is left
            console.log('unsubscribed');
            subscription.unsubsribe();
        };
    });