0

I am working with angular 12. Here is the angular html code.

    <ul id="respMenu" class="horizontal-menu">
            <ng-container *ngFor="let menuItem of menuItems; let i = index">
                 <ng-container *ngIf="IsAllowed(['admin','user'])">
                 <li>
                     <a>
                         <span class="title">{{menuItem.title}}</span>
                     </a>
                 </li>
             </ng-container>
             </ng-container>
         </ul>

i am calling this method show hide my li

  IsAllowed(allowedRoles){
    console.log("test");
    for (var i = 0; i < userRoles.length; i++) {
        if(allowedRoles.includes(userRoles[i])){
           return true;
        }
    }
  }

enter image description here

But it console the test text like thousands of times. why is this so please suggest me better way to do it.I Have 8 menuItems its shows 8 in UI but console many times.

IsAllowed Method parameter will be loaded dynamically and will be different for every item.

Faisal
  • 584
  • 3
  • 11
  • 33

6 Answers6

2

As Abhishek Priyadarshi already wrote ...

The problem is the change-detection of angular. It will call your method again and again, ... to check if the value changed.
One way to solve it, is to change the change-detection. ChangeDetectionStrategy.OnPush is the best option, but this may break your component. Please read the docs.

And here is the reason, why I wrote a new answer: You should write a new structural directive. So you can use the method on all components and have a "standard". Have a look to the strucural directive docs.

Here is a idea how your code can be look like:

HTML:

<ng-container *appAllowed="user,admin">
    <li>
        <a>
            <span class="title">{{menuItem.title}}</span>
        </a>
    </li>
</ng-container>

TS

@Directive({ selector: '[appAllowed]'})
export class AllowedDirective {

  constructor(private userService: UserService,
              private templateRef: TemplateRef<any>,
              private viewContainer: ViewContainerRef) { }

  @Input() set appAllowed(allowed: string) {
    var allowedList = allowed.split(',');

    for (let requiredRole of allowedList) {
       if (!this.userService.hasRole(required)) {
          this.viewContainer.clear();
          return;
       }
    }
    
    this.viewContainer.createEmbeddedView(this.templateRef);
  }
}

If you can't use the ChangeDetectionStrategy.OnPush, then you can reduce the workload, like this:

@Directive({ selector: '[appAllowed]'})
export class AllowedDirective {

  private roles: string[];

  constructor(userService: UserService,
              private templateRef: TemplateRef<any>,
              private viewContainer: ViewContainerRef) {
      userService.me().subscribe(me => this.roles = me.roles);
  }

  @Input() set appAllowed(allowed: string) {
    var allowedList = allowed.split(',');

    for (let requiredRole of allowedList) {
       if (!this.roles.includes(requiredrole)) {
          this.viewContainer.clear();
          return;
       }
    }
    
    this.viewContainer.createEmbeddedView(this.templateRef);
  }
}
akop
  • 5,981
  • 6
  • 24
  • 51
1

You should rewrite this as a pipe. Pipes are executed when a value changes, functions are executed every time change detection is run. More details can be found in this Stackoverflow question and answers.

Andrew Lewis
  • 5,176
  • 1
  • 26
  • 31
1

As per your requirement you need to check first if user logged in is admin or not and for that in your component :

get isAdmin(): boolean {
   if(this.user == 'admin') {
      return true;
   }
   return false;
}

Then in your HTML component :

<ng-container *ngIf="isAdmin">
     <li>
         <a>
            <span class="title">{{menuItem.title}}</span>
         </a>
     </li>
</ng-container>
Devang Patel
  • 1,795
  • 4
  • 21
  • i have added some more information to question. – Faisal Jul 19 '21 at 13:47
  • So function has nothing to do with menuItem so you can create two seprate variables in your .ts like isAdmin and isUser and return true/false for them using function in your component. – Devang Patel Jul 19 '21 at 14:06
1

You have to use TrackBy Function inside the ngFor loop.

On each ngDoCheck triggered for the ngForOf directive, Angular checks what objects have changed. It uses differs for this process and each differ uses the trackBy function to compare the current object with the new one. The default trackBy function tracks items by identity:

const trackByIdentity = (index: number, item: any) => item;

Example of trackBy inside *ngFor

<ng-container *ngFor="let menuItem of menuItems; let i = index; trackBy:trackByIdentity;">

How trackByIdentity funtion looks like on your controller

trackByIdentity(menuItem) {
    return menuItem.title; // here, you can track by any field.
}

If you're curious how ngFor works under the hood, read this answer.

Leandro Matilla
  • 911
  • 4
  • 14
0

You can use change detection strategy in component

@Component({
  // ...
  changeDetection: ChangeDetectionStrategy.OnPush
})
0

This should work, if I understood your question. It checks the users roles until it finds one that is allowed. If none found, returns false.

  IsAllowed(allowedRoles){
    userRoles.forEach(role => {
       if (allowedRoles.includes(role)) {
          return true
       }
    }
    return false
  }
Lars Rødal
  • 809
  • 1
  • 9
  • 26