-1

I have an Angular 7 app in which I have a post call and on the basis of that post call response I want to make guard active/inactive. I have my route guard like this

canActivate = (_router: ActivatedRouteSnapshot): boolean => {
    console.log('in link expiry guard')
    let userEmail = _router.paramMap.get('email');
    let isAllow;

    console.log('params : ', userEmail)
    userEmail = this._utilityService.decryptMsgByCryptoJs(userEmail);
    console.log('user email : ', userEmail)
    this._dataService.post(this._const.userResetPasswordLinkExpiry, { email: userEmail }).subscribe(resp => {
        console.log('verify response : ',resp)
        if (resp.success) {
            console.log('in success')
            isAllow = true;
        } else {
            isAllow = false;
        }
    })
    console.log('allow flag  : ',isAllow)
    if (isAllow) {
        console.log('in allow')
        return true;
    } else {
        console.log('in not allow')
        this._utilityService.navigate('/login');
        this._dataService.exhangeResetPasswordObsMsg({ event: 'linkExpired' });
        return false;
    }
}

But problem is that while my http post call is being in progress so my guard executes completely and returning false and after that response is coming from post call. How can I manage this scenario so I will make route true or false based on http post call response.

Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40
Fahad Hassan
  • 781
  • 10
  • 20
  • The problem is you are returning outside of a concurrent function and the code keeps evaluating. Return inside the post call instead of outside and return a ```Promise``` or an ```Observable``` instead of a boolean. CanActivate can return either of those -> https://angular.io/api/router/CanActivate – sinanspd Feb 25 '20 at 20:18
  • can you tell me in answer? – Fahad Hassan Feb 25 '20 at 20:22
  • see my updated question i have updated my route guard. But now it's giving an error on line `canActivate = (_router: ActivatedRouteSnapshot): Observable`. Saying that `function must return a value` – Fahad Hassan Feb 25 '20 at 20:24
  • Hii, You can use route reolsver instead of http inside gurad. – Rahul Rathore Feb 25 '20 at 20:26
  • you still need to return your call as a whole, see: https://stackoverflow.com/questions/37948068/angular-2-routing-canactivate-work-with-observable\ – sinanspd Feb 25 '20 at 20:27

1 Answers1

0

If you want to make an Http request inside your canActivate function, you need to return an Observable<boolean> instead of a boolean, since you are now performing an asynchronous action.

And since you want to navigate on fail, you should return Observable<boolean | UrlTree> instead.

Simple version

constructor(private router: Router) { }

canActivate(route: ActivatedRouteSnapshot, 
    state: RouterStateSnapshot): Observable<boolean | UrlTree> {
  return this.http.post(url, body).pipe(
    map((resp: any) => resp.success ? true : this.router.parseUrl('/path'))
  );   
}

We are returning the observable http request (the router will invoke it by subscribing), and mapping the response to either a

  • true - the router may proceed to the guarded route
  • UrlTree - the router should navigate to the route we have returned

Applied to your example

If we apply this to your example, we need to do a bit more work in the pipe, as you have an additional service call.

// TODO: inject other services
constructor(private router: Router) { }

canActivate(route: ActivatedRouteSnapshot, 
      state: RouterStateSnapshot): Observable<boolean | UrlTree> {
    const userEmail = route.paramMap.get('email');

    // I am assuming this is a synchronous call
    userEmail = this._utilityService.decryptMsgByCryptoJs(userEmail);

    const url = this._const.userResetPasswordLinkExpiry;
    const body = { email: userEmail };

    return this._dataService.post(url, body).pipe(
      // initial map from response to true/false
      map((resp: any) => resp.success),

      // perform an action if false
      tap(success => {
        if (!success) {
          // I'm assuming this is synchronous. If not, you will need to do a switchMap
          this._dataService.exhangeResetPasswordObsMsg({ event: 'linkExpired' });
        }
      }),
      // final map to boolean | UrlTree
      map(success => success ? true : this.router.parseUrl('/login'))
    );   
}

There are some service calls in there that I'm assuming are synchronous. This answer demonstrates how you perform an asynchronous call inside canActivate and either allow the router to navigate or return an alternative route to navigate to.

Kurt Hamilton
  • 12,490
  • 1
  • 24
  • 40