1

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.

kovac
  • 4,945
  • 9
  • 47
  • 90
  • Instead of `authService.getUserRoles` use `authService.getUserRoles()` – Sankalp Bhamare Jun 01 '20 at 13:44
  • @SankalpBhamare Your suggestion actually made the compiler error go away. But now, when I click on the button that has the anchor, the whole app freezes (browser also freezes). No errors on the console. I have to end task the browser. – kovac Jun 01 '20 at 13:51
  • "the whole app freezes (browser also freezes)" - that should be a new question. I have a feeling that the reason has something to do with the routes and the component under `/admin`. – mbojko Jun 01 '20 at 13:55
  • Kovac have you tested this function , inside componet body by getUserRoles().subscribe((t)=>console.log(t))? – Sankalp Bhamare Jun 01 '20 at 13:55
  • @SankalpBhamare I see `Array(1) ["admin"]` on the console. – kovac Jun 01 '20 at 14:00
  • @mbojko The component seems to be working fine. If I directly access the route it shows me the component just fine. – kovac Jun 01 '20 at 14:01
  • I can't seem to reproduce error , could you create a stackblitz page? – Sankalp Bhamare Jun 01 '20 at 14:04
  • Also could you try implementing getUserRoles() using the next,finish function? – Sankalp Bhamare Jun 01 '20 at 14:07
  • @SankalpBhamare I put a break point in getRoles function and keeps getting hit like infinitely many times (which is why the browser is freezing) – kovac Jun 01 '20 at 14:08
  • 1
    Now I know the problem, each time the view loads, it calls the function, and subscribes to the data, this happens each time when component re renders it self, it then again renders it self when the subscriber returns data causing an infinite loop which ultimately breaks / hangs application. – Sankalp Bhamare Jun 01 '20 at 15:15

2 Answers2

2

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();
}
Rachid O
  • 13,013
  • 15
  • 66
  • 92
mbojko
  • 13,503
  • 1
  • 16
  • 26
1

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>
StPaulis
  • 2,844
  • 1
  • 14
  • 24