Rxjs forkJoin не выполняется в Angular Route Guard

#angular #typescript #rxjs

#angular #typescript #rxjs

Вопрос:

Я создал службу пользовательских разрешений, которая извлекает разрешения с сервера для данного пользователя. Далее я создаю route guard, который использует эту службу, чтобы определить, имеет ли пользователь все разрешения, перечисленные в маршруте. Чтобы выполнить это, я перебираю разрешения, перечисленные в маршруте, и создаю наблюдаемый для каждого, когда запрашивается служба разрешений пользователя. Как только все наблюдаемые будут созданы, я forkJoin проверяю их, чтобы убедиться, что все они истинны.

В моем общем тестировании службы разрешений я могу каждый раз надежно получать результат. И если я вручную выполняю итерацию по возвращаемому массиву и подписываюсь на каждый наблюдаемый вручную, он работает по желанию. Однако, независимо от того, что я делаю, forkJoin никогда не появляется для выполнения. Если я заменю свои сгенерированные наблюдаемые массивом [of(true),of(true)] , он будет вести себя так, как ожидалось. Я не совсем уверен, что происходит.

Служба пользователя:

 @Injectable({
  providedIn: 'root'
})
export class UserPermissionsService {
  private _permissions = new BehaviorSubject<AppliedPermissions>(null);
  public permissionSnapshot: AppliedPermissions;
  public permissions: Observable<AppliedPermissions> = this._permissions.asObservable();

  constructor(private _userService: UserService) {
  }

  init(): Observable<AppliedPermissions> {
    return this._userService.getPermissions()
      .pipe(tap(p => {
        this._permissions.next(p)
        this.permissionSnapshot = p;
      }));
  }

  destroy(): void {
    this._permissions?.complete();
  }

  hasPermission(permission: string): Observable<boolean> {
    return this._permissions.pipe(
      switchMap(value => value ? of(value) : this.init()),
      map(response => {
        const perm = response.permissions
          .find(el => el.permissionName === permission);

        if (!perm)
          return false;

        return perm.allow;
      }),
      catchError(_ => of(false))
    );
  }

  inRole(role: string): Observable<boolean> {
    return this._permissions.pipe(
      switchMap(value => value ? of(value) : this.init()),
      map(response => {
        return !!response.roles.find(el => el === role);
      }),
      catchError(_ => of(false))
    );
  }
}
 

Защита маршрута

 @Injectable({providedIn: 'root'})
export class PermissionGuard implements CanActivate {
  constructor(private _userPermissionsService: UserPermissionsService) {
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
    const permissions = route.data.permissions as Array<string>;
    const observables = permissions.map(p => this._userPermissionsService.hasPermission(p));

    return forkJoin(observables)
      .pipe(
        tap(e => {
          console.log(e)
        }),
        map(e => e.every(v => v)),
        catchError(_ => of(false))
      );
  }
}
 

Ответ №1:

forkJoin выполняется только после завершения всех наблюдаемых. Поскольку hasPermission зависит от BehaviorSubject, который не завершается, то forkJoin никогда не выдается.

Вы можете использовать zip в сочетании с take(1) , чтобы делать то, что вы хотите.

 return zip(...observables)
  .pipe(
    take(1),
    tap(e => {
      console.log(e)
    }),
    map(e => e.every(v => v)),
    catchError(_ => of(false))
  );
 

Комментарии:

1. Как ни странно, zip демонстрирует точно такое же поведение, как forkJoin и было. Бросая точку останова на tap , кажется, что она никогда не выполняется.

2. Однако, похоже combineLatest , это действительно может дать мне то, что мне нужно. Но я не уверен, может ли это вызвать проблемы, если потенциально существует условие гонки.

3. Нет никакой теоретической причины (я могу придумать), почему combineLatest будет работать через zip, поскольку оба ожидают, пока все наблюдаемые не выдадут значение. Получаете ли вы какие-либо хиты при обратном вызове catchError?

4. Нет, никаких ошибок вообще. Метод tap никогда не вызывается. Ни одна из наблюдаемых в цепочке не выполняется.

5. Я допустил ошибку новичка. Вам нужно распространить zip(...observables) . Извините за это. Вот почему работает combineLatest. У него другая подпись.