Search code examples
angulartypescriptngforangular-ng-if

Angular *ngIf Condition inside *ngFor to show hide items


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.


Solution

  • 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);
      }
    }