简体   繁体   中英

Using defaultIfEmpty() still not emitting in Angular canActivate guard

I have an observable in a service:

hasAccess$: Observable<boolean>;

This gets assigned in a separate component. It has filter() in it and will never emit false, only true. So if it's false it doesn't emit.

I need to use it in a route guard. So I tried:

canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> {
    return this.service.hasAccess$.pipe(
      defaultIfEmpty(false),
      tap(access => {
        console.log({access}); // never logs
      }),
      first()
    );
  }
  • I used defaultIfEmpty() to ensure a value is emitted
  • I used tap() to log if anything happens (it doesn't)
  • I used first() because observables returned in guards must complete .

I ensured this.service.hasAccess$ gets assigned. It throws an error if it isn't because you can't pipe off undefined. There's no error. Now the weird thing is, this emits false:

return of().pipe(
  defaultIfEmpty(false),
  tap(access => {
    console.log({access});
  }),
  first()
);

Both are piped to defaultIfEmpty(false) . Why isn't the guard emitting?

Update : Thanks to @martin for pointing out that defaultIfEmpty() only emits on completion. Also I realize now that first() won't make it complete if it doesn't emit anything. I guess I need to figure out how to make it complete when it doesn't emit anything.

You can add startWith(false) operator to your hasAccess$ observable, so it will always emit something:

hasAccess$: Observable<boolean> = this.someServiceCall().pipe(startWith(false));

Or you can introduce a timeout in the canActivate() method:

canActivate(
  next: ActivatedRouteSnapshot,
  state: RouterStateSnapshot): Observable<boolean> {
  return this.service.hasAccess$.pipe(
    timeout(5000),
    first(),
    catchError(() => of(false))
  );
}

If I were you, I'd use the first method if hasAccess$ is getting its value from a store or a synchronous source, and method 2, if it is making an asynchronous call.

Figured out a solution:

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean> {
    return this.service.hasAccess$.pipe(
      merge(of(false)),
      first()
    );
  }
  1. It starts by piping off of hasAccess$ ,
  2. It merges with an observable of false ,
  3. It uses first() so that if hasAccess$ emits true, it takes it, else if it emits nothing, the guard gets false.

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