0

I have PermissionsService which stores user's permissions. These are loaded from API by HttpClient after user's successful auth process (after login I call the getUserGroupsWithModelPermissions() which loads user's permission groups, and then I sum these permissions into _resolvedUserPermissions BehaviorSubject)

In every component I need these user's permissions, I subscribe to resolvedUserPermissions$, and after receiving data, I use other methods (ex. resolveModelPermission() where I pass data from subscribe) from ModelPermissionsService to do business logic and it works well.

Example usage in components:

  ngOnInit() {
    this.$permissions = this.permissionsService.resolvedUserPermissions$.subscribe(data => {
      this.canSeeProducts = this.permissionsService.resolveModelPermission(data, 'Products', PermissionScope.Read);
    })
  }

Permissions Service:

export class ModelPermissionService {

  private _resolvedUserPermissions = new BehaviorSubject<ModelFieldPermissions[]>([]); 
  public resolvedUserPermissions$ = this._resolvedUserPermissions.asObservable();

  public getUserGroupsWithModelPermissions(): void {
    this.httpClient.get<ApiResponse<UserGroups[]>>(userEndpoint + '/me/permissions', httpOptions)
    .subscribe({
      next: data => {
        this._resolvedUserPermissions.next(this.sumUserPermissions(data.data));
      },
      error: err => {
        console.error(err);
      }
    })
  }

public resolveModelPermission(userPermissions: ModelFieldPermissions[], modelName: string, permissionScope: PermissionScope):boolean {
    if(userPermissions.length > 0){
      // DO LOGIC HERE
    }
    return false;
  }


}

Now I need to use data from resolvedUserPermissions$ in canActivate method in router guard service but I don't know how to subscribe to this resolvedUserPermissions$?

I need something like this:

canActivate(){
    this.modelPermissionService.resolvedUserPermissions$.subscribe(data =>{
        return this.modelPermissionService.resolveModelPermission(data, 'modelName', scope)
    })
}

All methods I found used pipe+map instead of subscribe but I had no permissions data. Example:

canActivate(){
    return this.getAccess()
}

getAccess(){
    this.permissionsService.resolvedUserPermissions$.pipe(
        map(data =>{
            //here data is empty, no logic to do, because no subscription?
        })
    )
}
  • You don't need to subscribe in `canActivate`. Just return Observable that emits boolean and completes. As side note, subscribing in service is bad idea. Mostly you don't need to subscribe at all, create operator chain with pipe() that creates desired result. – Edmunds Folkmanis Feb 22 '23 at 16:02
  • why ` getUserGroupsWithModelPermissions` is creatinga subscripiton? Your code is perfect example of how not to use Rx in your app. – Antoniossss Feb 22 '23 at 16:45
  • @Antoniossss I saw such a solution in many courses and tutorials. I have more than 20 components using this data, what you would suggest to do instead of this? – reverterez Feb 22 '23 at 16:58
  • Stop using those courses. – Antoniossss Feb 22 '23 at 17:12

2 Answers2

1

Have you tried this?

canActivate(){
    return this.modelPermissionService.resolvedUserPermissions$.pipe(
         switchMap(data => this.modelPermissionService.resolveModelPermission(data, 'modelName', scope))
    )
}
Antoniossss
  • 31,590
  • 6
  • 57
  • 99
  • Throws an error `Type 'boolean' is not assignable to type 'ObservableInput'.ts(2322)`, so I tried `switchMap(async (data) => this.modelPermissionService.resolvePropertyPermission(data, 'modelName', scope))` but it doesn't work. I have no data in `data`. – reverterez Feb 22 '23 at 15:36
0

Permission Service:

export class ModelPermissionService {

 getUserGroupsWithModelPermissions(): Observable<ModelFieldPermissions[]> {
    this.httpClient.get<ApiResponse<UserGroups[]>>('yourpath', httpOptions)
      .pipe(
        map(data => this.sumUserPermissions(data.data)),
        shareReplay(1), // probably you want to hold and re-use result from api
        tap(data=> concole.log(data)), // DEBUGGING
      );
  }

  resolveModelPermission(data, 'modelName', scope): Observable<boolean> {
    return this.getUserGroupsWithModelPermissions().pipe(
      map(data=> / your code to extract permission from user data /),
    )
  }
}

in Resolver Service:

canActivate() {
    return this.modelPermissionService.resolveModelPermission(data, 'modelName', scope)
      .pipe(
        take(1), // Observable must complete!
        tap(data=> concole.log(data)), // DEBUGGING
      ),
}
  • Ok, it has worked, but now how to use this in my other many (more than 20) components? I tried something like this `ngOnInit() { this.permissionsService.resolveModelPermission2(someData, 'Orders', PermissionScope.Read).pipe( take(1), tap(data =>{ this.canSeeOrdersDetails = data}) ) }` But I can't see any effects – reverterez Feb 22 '23 at 16:57
  • Do I have to subscribe at the end? – reverterez Feb 22 '23 at 17:11
  • No that's not necessary – Pieterjan Feb 22 '23 at 22:34
  • Router internally subscribes to Observable returned from `canActivate`. In component you must subscribe to get results (and, of course, unsubscribe). Or, even better, use `async` pipe to do the job. – Edmunds Folkmanis Feb 23 '23 at 07:22
  • Ok I get this, thank you very much – reverterez Feb 23 '23 at 09:27