【问题标题】:Rxjs forkJoin not executing in Angular Route GuardRxjs forkJoin 未在 Angular Route Guard 中执行
【发布时间】:2021-01-20 20:43:09
【问题描述】:

我已经构建了一个用户权限服务,该服务从服务器获取给定用户的权限。我进一步构建了一个路由守卫,它利用此服务来确定用户是否具有路由中列出的所有权限。为此,我遍历路由上列出的权限,并在查询用户权限服务时为每个权限创建一个 observable。一旦所有的 observables 被创建,我forkJoin 确保它们都是真实的。

在我对权限服务的一般测试中,我每次都能可靠地返回结果。如果我手动迭代返回的数组并手动订阅每个 observable,它会按需要工作。但是,无论我做什么,forkJoin 似乎都不会执行。如果我用 [of(true),of(true)] 的数组替换我生成的 observables,它会按预期运行。我不太清楚发生了什么。

用户服务:

@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))
      );
  }
}

【问题讨论】:

    标签: angular typescript rxjs


    【解决方案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))
      );
    

    【讨论】:

    • 奇怪的是,zip 表现出与forkJoin 完全相同的行为。在tap 上抛出一个断点,它似乎永远不会执行。
    • 然而,看起来combineLatest 可能实际上给了我我想要的东西。但我不确定如果存在潜在的竞争条件,这是否会导致问题。
    • 没有任何理论上的理由(我能想到)为什么 combineLatest 会在 zip 上工作,因为两者都等到所有可观察对象都发出一个值。你在 catchError 回调中得到任何命中吗?
    • 不,完全没有错误。 tap 方法永远不会被调用。链中的所有 observables 都没有。
    • 我犯了一个初学者的错误。您需要传播zip(...observables)。对此感到抱歉。这就是 combineLatest 起作用的原因。它有不同的签名。
    猜你喜欢
    • 2021-03-25
    • 2019-12-14
    • 2017-07-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-10
    • 2020-01-15
    相关资源
    最近更新 更多