kovac
kovac

Reputation: 5389

Observable<string[]> in ngIf directive

I have a method that returns roles of a user like:

  getUserRoles() : Observable<string[]> {
    return this.getToken().pipe(
      map(token => {
        let payload = decode(token);
        return payload["roles"];
      })
    )
  }

I'm trying to use this in an anchor to hide/show the item based on a role like:

<a *ngIf="(authService.getUserRoles | async).includes('admin')" routerLink="/admin" clrDropdownItem>Admin</a>

However, I get the compiler error:

ERROR in src/app/components/profile/profile.component.html:15:18 - error TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type '() => Observable<string[]>' is not assignable to parameter of type 'Promise<unknown>'.
      Type '() => Observable<string[]>' is missing the following properties from type 'Promise<unknown>': then, catch, [Symbol.toStringTag], finally

15       <a *ngIf="(authService.getUserRoles | async).includes('admin')" routerLink="/admin" clrDropdownItem>Admin</a>
                    ~~~~~~~~~~~~~~~~~~~~~~~~~

  src/app/components/profile/profile.component.ts:7:16
    7   templateUrl: './profile.component.html',
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~
    Error occurs in the template of component ProfileComponent.

Can't quite figure out what I'm doing wrong. I'm using Angular 9.

Upvotes: 1

Views: 883

Answers (2)

mbojko
mbojko

Reputation: 14679

Ah, I think I get it!

I had a somewhat similar problem once, also with a function call in the template. Long story short, change detection triggered change detection, which is more or less while (true);.

Try changing this

<a *ngIf="(authService.getUserRoles() | async).includes('admin')" ...

into something like

<a *ngIf="userIsAdmin" ...

and in the TS part of the component

userIsAdmin = false;
onDestroy = new Subject();

ngOnInit() {
    this.authService.getUserRoles().pipe(takeUntil(this.onDestroy)).subscribe(/* assign value to this.userIsAdmin here
}

ngOnDestroy() {
    /* To prevent a memory leak */
    this.onDestroy.next(); 
    this.onDestroy.complete();
}

Upvotes: 2

StPaulis
StPaulis

Reputation: 2916

Your approach won't work...

Even if you change your code to this: authService.getUserRoles() | async Your code won't work because this function would run every time your view is checked and you will not benefit from the async pipe, the opposite.

It would be better

1) Subscribe to your data in the initilazition of the component (remember to unsubscribe before you destroy the component).

2) Create new pipe to handle that logic.

3) Use guards.

4) Make the function synchronous:

.ts
    isInRole(role: string): boolean {
        const token = this.getToken();
        const payload = decode(token);
        return payload && payload["roles"] && payload["roles"].includes(role);
    }

.html

     <a *ngIf="isInRole('admin')" routerLink="/admin" clrDropdownItem>Admin</a>

Upvotes: 1

Related Questions