Guard canActivate runs before service is instantiated

This is quite possibly a very trivial problem, and I'm just picking up Angular 2+ coming from an AngularJS background.

In it's simplest, I'm implementing authentication into my angular 9 app and stumbling across an issue with my guard.

In my authentication.service.ts service I'm using a BehaviourSubject of type user, and I'm saving my User object in localStorage using this common library: https://github.com/cyrilletuzi/angular-async-local-storage

In my authentication.service constructor I'm doing something like this:

constructor(private request: RequestService, private storage: StorageMap) {
    this.storage.get('currentUser').subscribe((user: User) => {
      this.currentUserSubject = new BehaviorSubject<User>(user);
      this.currentUser = this.currentUserSubject.asObservable();
    });
  } 

and my isAuthenticated method:

isAuthenticated() {
    return this.currentUserSubject.value;
  }

This works great, however, because storageMap is an observable it's taking a tiny amount of time to return, and thus when I try to check the user is authenticated in my guard:

canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.authService.isAuthenticated();
  } 

Unfortunately this.currentUserSubject is undefined. So the guard is running before the constructor in authentication.service has had a chance to instantiate properly.

Any points would be gratefully received.

UPDATE:

I think i'm getting closer (ish).

in my authentication.service my isAuthenticated method now looks like this (as suggested below)

isAuthenticated() {
return this.currentUser
  .pipe(filter(user => user));

}

Albeit, with an error: Arguement type user is not assignable to parameter type.

Also, if i change my return statement in canActivate:

canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.authService.isAuthenticated();

}

But i get this in a console

Type 'Observable' is not assignable to type 'Observable'. Type 'unknown' is not assignable to type 'boolean | UrlTree'. Type '{}' is missing the following properties from type 'UrlTree': root, queryParams, fragment, queryParamMap

18     return this.authService.isAuthenticated();

3 answers

  • answered 2020-05-24 10:31 ionut-t

    The canActivate method must return Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree.

    As far as I can tell you're returning an object (user).

    However, in your guard, you should use the observable instead of the subject.

    canActivate(
        next: ActivatedRouteSnapshot,
        state: RouterStateSnapshot): Observable<boolean> {
        return this.authService.currentUser.pipe(map(user => !!user));
      } 
    

    The double negation (!!) from the map operator will make the observable to return a boolean.

  • answered 2020-05-24 11:23 Berk Kurkcuoglu

    Guards get activated upon navigation, so just make sure you have the user set before you navigate to your post login page.

    constructor(private authenticationService: AuthenticationService, private router: Router) {}
    
    ngOnInit() {
        this.handleUserChange();
    }
    
    private handleUserChange(): void {
        this.authenticationService.currentUserSubject
        .pipe(filter(user => user)) // falsy value check
        .subscribe(user => 
        {
             // Navigate to post login
             this.router.navigate(['home-page']);
        }
    }
    

  • answered 2020-05-24 13:31 Aakash Garg

    async isAuthenticated() {
        var res = await this.storage.get('currentUser').toPromise();
        return res;
    }
    

    Reason for behaviour you was facing is, the subscription is a asynchronous job. so class initialization got complete as soon as it called subscribe. Subscription's callback will be called later when it will have that data. Before call of your callback your got called.