Search code examples
angularangular-promiseangular-httpclientangular-observableangular-router-guards

Angular: RouterGuards, HTTP requests and race conditions


I'm trying to create a RouterGuard in Angular that gets a response from the backend about the User's login status. The problem is that the HTTP request returns a Subscription with async concrete values. How can I guarantee the Router will "wait" for the http call to be transformed into a real value and then be processed?

AlreadyLoggedRouterGuard:

export class AlreadyAuthIn implements CanActivate {
  constructor(private clientService: ClientService,
              private router: Router) {  }
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {

return this.clientService.isUserLogged()
  .subscribe(
    (response: Response) => {
      let loginStatus = response.message;
      if (loginStatus == 'not_logged') {
        localStorage.clear();
        return false;
      } else {
        return true;
      }
    });
}

Note: clientService.isUserLogged() is the http call, and will return a Observable.

This code already shows an Error saying that should not return a Subscription. Then I tried to assign the logic to a variable like:

let logged = this.clientService.isUserLogged().subscribe(...<logic>......)
if (logged) {
  return false;
} else {
  return true;
}

But that creates a new problem: race condition. The If statement is checked before the Subscription turns into a Boolean value, and the program processes the logic wrongly. Maybe we can solve this with a Promise?


Solution

  • You are now returning a subscription not the result of your request. Use a map function to turn the return type of your request into an Observable<boolean> which will be used by angular to handle the route guard.

    Try something like (untested)

    export class AlreadyAuthIn implements CanActivate {
      constructor(private clientService: ClientService,
                  private router: Router) {  }
      canActivate(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    
    return this.clientService.isUserLogged()
      .pipe(map((response: Response) => {
          let loginStatus = response.message;
          if (loginStatus == 'not_logged') {
            localStorage.clear();
            return false;
          } else {
            return true;
          }
        });
    }