【问题标题】:Angular guard based on two observables基于两个可观察对象的角度防护
【发布时间】:2021-10-25 11:31:43
【问题描述】:

我正在尝试做一个“AdminGuard”,这应该基于两件事:

  1. 用户是否登录?
  2. 用户是否有管理员权限?

我有一个AuthService,它提供两个Observable

我做了以下事情:

@Injectable({
  providedIn: 'root'
})
export class IsAdminGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router) { }


  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> {
      console.log(this.auth)

      return combineLatest([this.auth.isLoggedIn, this.auth.isAdmin]).pipe(
        take(1),
        map((authInfo) => {
          console.log(authInfo)
          if (!authInfo[0]) {
            console.error('Access denied - Unauthorized')
            return this.router.parseUrl('/auth/');
          } else if (!authInfo[1]) {
            console.error('Access denied - Admin only')
            return this.router.parseUrl('/auth/unauthorized');
          } else {
            return true;
          }
        })
      );
  }

}

console.log(this.auth) 被调用并且似乎具有有效值,但第二个 console.log 从未被调用并且我的组件未加载。

如果我从路线中移除警卫:

  {
    path: 'admin',
    component: AdminComponent,
    //canActivate: [IsAdminGuard],
  }

它有效,所以我很确定是 IsAdminGuard 不起作用。

我还根据相同的布尔值显示其他一些东西(一些 *ngIf="authService.IsLoggedIn | async" 正在工作,所以我真的不明白我搞砸了什么?

编辑 以下是我如何在我的 AuthService 中更新 IsLoggedIn/IsAdmin/IsUser 的不同值:

 constructor(public afAuth: AngularFireAuth, public router: Router, private afStore: AngularFirestore) {
    this.afAuth.authState.subscribe(async user => {
      console.log('handling auth')
      if (this._roleSubscription) {
        this._roleSubscription.unsubscribe();
        this._roleSubscription = undefined;
      }
      if (user) {
        this._user.next(user);
        this._isLoggedIn.next(true);
        this._roleSubscription = this.afStore.doc<Roles>(`roles/${user.uid}`).valueChanges().subscribe(role => {
          console.log('updating roles', role)
          if (role) {
            this._isAdmin.next(role.admin == true)
            this._isUser.next(role.admin == true || role.user == true);//Admin have also an heart, they are users too!
          } else {
            this._isAdmin.next(false);
            this._isUser.next(false);
          }
        });
      } else {
        this._user.next(undefined);
        this._isLoggedIn.next(false);
        this._isAdmin.next(false);
        this._isUser.next(false);
        await this.router.navigate(['/auth']);
      }
      console.log('values updated')
    })
  }

【问题讨论】:

  • 你能展示一下 observables 的AuthService 实现吗? Observables 何时被输入值?你的警卫很可能在此之前运行,你应该将AuthService 逻辑移动到APP_INITIALIZER
  • combineLatest 的所有源 Observables 必须至少发射一次,这是我首先要看的地方。
  • @Eldar 我不这么认为,因为我没有看到console.log(authInfo),所以并不是我错过了更新,我只是从来没有得到任何价值
  • 如果你想在两个 observable 都发出一个值后发出一次,也许可以尝试使用forkJoin 而不是combineLatest
  • @PoulKruijt 我添加了我的服务的更新部分

标签: angular rxjs guard angular2-observables


【解决方案1】:

您必须使用发出最新值的ReplaySubject。主题仅在有活动订阅时才发出,BehaviorSubject 总是在以初始值开头时发出

readonly _isLoggedIn = new ReplaySubject<boolean>(1);
readonly _isAdmin = new ReplaySubject<boolean>(1);

【讨论】:

  • 你是对的!我最初认为 ReplaySubject 在构造函数中带有 1 将与 BehaviorSubject 相同,但事实并非如此!但不同的是,您不必提供初始值,然后您可以确保它至少被初始化一次!太棒了,谢谢!
  • @J4N 确切地说,1 表示新订阅者在订阅时应该收到多少“历史帧”。不管之前发生了什么,你总是想要最新的值,所以 1 是你所需要的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-12
  • 1970-01-01
  • 2019-12-05
相关资源
最近更新 更多