1

this question is somehow related to this questions 1,2 , but they do not addressed the subject of synchronous observables in javascript in a manner that can help me.

I'm currently in angular 4 or just angular and i have a protected route waiting for two guards to resolve. the two of them returns true as expected but the second one takes too long so it's answer comes after the canActivate method has finished. example code below. what can i do to wait for my second guard to synchronously resolved?

I'm new to this javascript and observables async way of doing things! Sorry if this question is silly.

  1. first authGuard:

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 
    boolean {
         if(this.cookieService.check('token'))return true;
         console.log(true); //For demostration purpose
    }
    
  2. second authGuard:

    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): 
    boolean {
         this.roleService.checkRole('admin') // this service take some time
         .subscribe(
             response=>{
                 //Simple logic checking the response.
                 console.log(true); 
                 return true;
             },
             error=>{
                 //some logic with the error
                 console.log(false);
                 return false;
             }
    
         )
    console.log('End of authguard method');
    }
    
  3. roleService:

    checkRole(role:string): 
    boolean {
           return  this.http.post(Url, body,requestOptions)
                            .map(this.processData)
                            .catch(this.handleError);
    }
    

4.Console shows:

    true                        // from authGuard 1
    "End of authguard method"   // from authGuard 2
    true                        // from authGuard 2

The router can not navigate to the desire route because of the second true coming too late. I also have tried the first() operator before subscribing and the first operator do not wait for the observable in the role.Service to resolve.

ramon
  • 35
  • 4
  • 1
    "Synchronous observable" is an oxymoron. –  Jul 24 '17 at 02:44
  • 2
    @torazaburo it's not really, though. RxJS makes no guarantees about which tick their code (or the code of their subscribers) will run in. If the observable is from a strictly synchronous source, then the whole chain might be run inline on the current stack. If the source of the observable is asynchronous, or you're flatMapping (switching, whatever) in an asynchronous source, then your chain is asynchronous, and will happen in a future set of stacks at that point. Promises guarantee constructors are sync and all resolutions are async; no such luck with RxJS. – Norguard Jul 24 '17 at 03:17
  • @paulpdaniels in the angular docs [angular example](https://angular.io/generated/live-examples/router/eplnkr.html) they use the canActivate method as boolean. nevertheless i tried your suggestion but the problem is that my second authGuard return true after the canActivate method is done and because of this the route can not be access. – ramon Jul 24 '17 at 17:40
  • @ramon what do you mean it is `returning true after the canActivateMethod` is done? You should be returning an `Observable` which angular knows to wait for completion of before evaluating the `canActivate`. Unless I have misunderstood some part of the docs – paulpdaniels Jul 24 '17 at 21:40
  • @paulpdaniels angular is not waiting for `this.roleService.checkRole` if you put a console.log('something') at the end of the canActivate method, `something` is printed before the answer from the `this.roleService.checkRole` . I think i will end up refactoring my entire code just to work a round this issue. – ramon Jul 26 '17 at 02:18
  • @ramon that is expected. When you return an `Observable` from `canActivate` it has *not* resolved yet, so the console will naturally be printed before you get the return value, because the checkRoles will not block the execution of the script. – paulpdaniels Jul 26 '17 at 02:23

1 Answers1

1

The interface for CanActivate is

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

Which means that it will accept a return type of a Promise or and Observable in addition to a raw boolean value. The issue that you are facing is that you are trying to treat an Observable as though it is a boolean value, which it is not. You should instead refactor your second authGuard to be:

canActivate(
  route: ActivatedRouteSnapshot, 
  state: RouterStateSnapshot
): Observable<boolean> {
  return this.roleService
     .checkRole('admin') // this service take some time
     // Any success value is mapped to true
     .mapTo(true)
     // Any error will log and return false
     .catch(e => {
       console.log('Encountered an error checking role', e);
       return Observable.of(false);
     })
     // This logs when this *actually* completes
     .finally(() => console.log('End of authguard method'));
}

The reason we need to do the above is because as a general pattern we don't know when an Observable will emit, we just know what we want to do when it does. Instead of trying to assign a single value to return, we are actually returning a pipeline of work that will be accomplished eventually.

paulpdaniels
  • 18,395
  • 2
  • 51
  • 55