简体   繁体   中英

Angular - How to make canActivate to await for promise response?

I try to check in CanActivate gurad if user is logged in.
If yes, then I navigate him to some page.
If not, then I navigate him to login page.
I use promise.

I have a service named LoginService :

export class LoginService {

  urlcheck = 'http://localhost:80/isconnected.php';
  resultCheck;

  constructor(private http: HttpClient, private router: Router)
  {
  }

  async checkLogin()
  {
    const x = await this.updateConnectedState();
    return x;
  }

  updateConnectedState()
  {
    //formData here...
    return this.http.post(this.urlcheck, formData).toPromise()
      .then(
        (response) =>
        {
          this.resultCheck = response; 
          console.log(response);
          return this.resultCheck.state; //state is true/false
        },
        (error) => 
        { 
            console.log(error); 
            return false;  
        }
      );
    }
  }


the canActivate Gurad:

export class BackHomeGuardService implements CanActivate
{
  constructor(private logService: LoginService, private router: Router)
  {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|boolean
  {
    const result = this.logService.checkLogin();
    console.log( result);

    if (result)
    {
      console.log('true');
      // navigate to hom page
      return true;
    }
    else
    {
      console.log('false');
      // navigate to login page
      return false;
    }
  }
}

The guard canActivate function always return true.
I checked in console and the the promise result is:

__zone_symbol__state: null
__zone_symbol__value: []

I want to make the canActive await for result before it will do something.
What if my backend (php code file) will take 30 seconds to be completed?
I need a solution. Thanks.

According to CanActivate 's type:

export declare interface CanActivate {
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree;
}

the method canActivate can return Observable, Promise or boolean.

You can simply return your checkLogin() call like:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|boolean {
    return this.logService.checkLogin();
}

and everything will work.


UPDATE

answering the question "how do I handle the returned boolean in my canActivate" from comments

The idea of the CanActivate is to restrict the access to some routes due to a certain condition. You can perform some side-effects (but I'm not sure how they will be displayed, unless it's a console.log) by chaining your Promise or Observable:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>|boolean {
    return this.logService
      .checkLogin()
      .then(canLogin => {
         console.log('Can user login:', canLogin);
         // perform you side effects here
      })

}

If you look at the return type of the canActivate method:

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

}

It can return a simple boolean or a Promise, or an Observable. Also, I don't think you need to convert the Observable to a Promise inside your LoginService.

canActivate can work with observable so you can map query result to return observable in this way

import { of, Observable } from 'rxjs';
import { catchError, map } from 'rxjs/operators';

export class LoginService {

  urlcheck = 'http://localhost:80/isconnected.php';
  resultCheck;

  constructor(private http: HttpClient, private router: Router) {
  }

  checkLogin(): Observable<boolean> {

    //formData here...
    return this.http.post(this.urlcheck, formData).pipe(
      map(
        (response) => {
          this.resultCheck = response;
          console.log(response);
          return this.resultCheck.state; //state is true/false
        }),
        catchError(error => {
          console.log(error);
          return of(false);
        })
      );
  }
}

export class BackHomeGuardService implements CanActivate
{
  constructor(private logService: LoginService, private router: Router)
  {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>
  {
    return this.logService.checkLogin();
  }
}

canActivate can have the following return type: boolean , Promise<boolean> , or Observable<boolean> .

Since you already have checkLogin() to return a Promise that will resolve to true/false . Let's stick with Promise or Observable . As the others have already answered, you can absolutely just return this.logService.checkLogin()

   canActivate(): Promise<boolean> {
      return this.logService.checkLogin();
   }

Now, I see that you want to navigate somewhere else based on the checkLogin() value as well. And I'd suggest to convert the Promise to Observable so you can leverage tap operator. You can do it like the following:

   return fromPromise(this.logService.checkLogin()).pipe(
      tap(isLogin => {
         if (!isLogin) {
            // not login. navigate to login page
         }
      })
   )

We do not really want to navigate somewhere on true because when it's true, the Guard will navigate the user to the route that is being guarded.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM