API call in canactivate method in Angular

Viewed 5898

I am using canActivate using guards in Angular. I want to check if the user is authenticated and based on the result protect the route. There are two types of users: Type1 and Type2, so user can be either authenticated with Type1, Type2 or unauthenticated. The following guard is for Type1 user.

Here is my code:

constructor(private authservice: AuthService, private router: Router, private route: ActivatedRoute){}
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{

        const self = this;
        const expectedType = "type1";

        this.authservice.isUserAuthenticatedbyType(expectedType).then(
            function(data){
                if(data === false){
                    console.log(data);
                    self.router.navigate(['/'], {relativeTo: self.route});
                }
                return data;
            },
            function(error){
                self.router.navigate(['/']);
                return false;
            }
        );
        return false;
    }

The problem is I make an API call to validate if the user is authenticated and return false; is executed before the result from the API. So, momentarily I see a different page and then it is routed to the correct page. How can I fix this, I do not want to return false or true before the API call, but not doing that gives an error.

I also tried the following:

return this.authservice.isUserAuthenticatedbyType(expectedType)

But this simply navigates me to the http://localhost:4200 url in case of unauthenticated user.

I have the following route:

{ path: "", component: HomeComponent },

So, in the above scenario, HomeComponent should have been called, but ngOnInit of HomeComponent is not getting called.

3 Answers

You can also try like this:

canActivate(): Observable<boolean> | Promise<boolean> | boolean {
    return new Promise(res => {
        this.authservice.isUserAuthenticatedbyType("type1").subscribe(
            (data) => {
                if (data === true) {
                    res(true);
                } else {
                    this.router.navigate(['/']);
                    res(false);
                }
            },
            (error) => {
                this.router.navigate(['/']);
                res(false);
            }
        );
    });
}

You can achieve it like this:

Angular <= 7.0.0

public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  return this.authservice.isUserAuthenticatedbyType("type1").pipe(
    map(data => {
      if (data === false) {
        this.router.navigate(['/']);
        return false;
      }

      return !!data;
    }),
    catchError(() => {
      this.router.navigate(['/']);
      return of(false);
    }),
  );
}

Angular >= 7.1.0

Starting with Angular 7.1.0 (note that it's not in 7.0.x), you can also do this instead, which is shorter and more predictable if you have multiple guards:

public canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
  return this.authservice.isUserAuthenticatedbyType("type1").pipe(
    map(data => data === false ? this.router.parseUrl("/") : !!data)
    catchError(() => this.router.parseUrl("/")),
  );
}

If you are using promise try something like this - main idea is to hold your routing until your Api call is done - I had the same issue, I have achieved it by returning Promise<boolean> on my route gaurds

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean{
    const self = this;
    const expectedType = "type1";
    retrun new Promise(res => {
      this.authservice.isUserAuthenticatedbyType(expectedType).then(
        function (data) {
          if (data === false) {
            console.log(data);
            self.router.navigate(['/'], { relativeTo: self.route });
          }
          res(data);
        },
        function (error) {
          self.router.navigate(['/']);
          res(false);
        }
      );
    });
  }

This method solved my issue - it waits until the API returns data and gives direction to the route

Hope it will work - Happy coding !!

Related