1

I'm trying to select two fields from the ngrx store in an Angular Guard like this:

@Injectable()
export class RoleGuard implements CanActivate {

  constructor(
    public router: ActivatedRouteSnapshot,
    private store: Store<AppState> ) {}

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {

    const expectedRole = route.data.Role;

    return combineLatest(
        this.store.pipe(select(isLoggedIn)),
        this.store.pipe(select(currentUser)),
      ).pipe(
          tap( ([loggedIn, user]) => 
                {
                    if ( loggedIn && !(user.role.find(expectedRole) >= 0) ) {
                        this.router.navigateByUrl('/error/403')
                    };
                }
            )
        );


  }

}

However, I'm getting Type 'boolean | [any, any]' is not assignable to type 'boolean', which makes sense, since the combineLatest return the result in array. But I can't seem to find a more elegant way than combineLatest, instead of nesting the two select observable, what would be my alternatives here?

Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
Yehia A.Salam
  • 1,987
  • 7
  • 44
  • 93

3 Answers3

1

You can use forkJoin

forkJoin([
        this.store.pipe(select(isLoggedIn)),
        this.store.pipe(select(currentUser)),
      ])

Or merge operator

Creates an output Observable which concurrently emits all values from every given input Observable.

import { merge, interval } from 'rxjs';
import { take } from 'rxjs/operators';

const timer1 = interval(1000).pipe(take(10));
const timer2 = interval(2000).pipe(take(6));
const timer3 = interval(500).pipe(take(10));
const concurrent = 2; // the argument
const merged = merge(timer1, timer2, timer3, concurrent);
merged.subscribe(x => console.log(x));
Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
  • for some reason ```forkJoin``` didn't execute the function in the map, though ```combineLatest``` did, not sure why https://gist.github.com/yehiasalam/c364423bca07170a61c343fa2d9157c6 – Yehia A.Salam Aug 17 '19 at 11:50
1

canActivate method should return a boolean wrapped in an Observable. As per your code, it is returning the values wrapped in an Observable returned from the combineLatest method which is an array. You can use map operator to return true or false like this:

@Injectable()

export class RoleGuard implements CanActivate {

  constructor(
    public router: ActivatedRouteSnapshot,
    private store: Store<AppState> ) {}

  canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {

    const expectedRole = route.data.Role;

    return combineLatest(
        this.store.pipe(select(isLoggedIn)),
        this.store.pipe(select(currentUser)),
      ).pipe(
          map( ([loggedIn, user]) => 
                {
                    if ( loggedIn && !(user.role.find(expectedRole) >= 0) ) {
this.router.navigateByUrl('/error/403')
                      //I am assuming that you want to fail the guard and want your application to route to 403 page, so let’s return false
                      return false;
                    };

                  //I am assuming that you want to pass the guard of above if condition fails, so return true; bottom line is to return true/false as per your logic.
                  return true;

                }
            )

);
  }

}

Hope it helps.

user2216584
  • 5,387
  • 3
  • 22
  • 29
1

forkJoin can be used when both service are independent & is best used when you have a group of observables and only care about the final emitted value of each. If response of one service is used/consumed by other service then below code snippet will work.

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {

return this.store.pipe(select(isLoggedIn)).pipe(switchMap(loggedIn)=>{
     return _checkLoggedIn(loggedIn);
   }),
   catchError(() => {
      return of(false);
    }));
}


private _checkLoggedIn(loggedIn): Observable<boolean>{
  if(loggedIn){
    return of(true);
  }else{
    return this.store.pipe(select(currentUser)).pipe(map((currentUser)=>{
      return currentUser;
    }));
  }
}


penDrive
  • 9
  • 2