Search code examples
angulartemplatesoptimizationngforangular-changedetection

What are better ways to avoid using method calls within Angular Templates?


I am trying to avoid using method calls within Angular Templates as they are non-performant there. Lets say I have a list of names:

const names: string[] = ['Billy', 'Mandy', 'Carl', 'Sheryl']

and in my template im using an ngFor to iterate the list and print the names:

<ng-container *ngFor="let name of names">
  <p>{{ name }}</p>
</ng-container>

But now I need to only display the name if it starts with an 'S' so I change to:

<ng-container *ngFor="let name of names">
  <p *ngIf="doesNameStartWithS(name)">{{ name }}</p>
</ng-container>

Now I have a method in my template which I know is going to run more times than is necessary. To avoid this I could do something like:

// this runs whenever the names list changes
const nameStartsWithSList: boolean[] = this.names.map((name: string): boolean => this.doesNameStartWithS(name));

and then change my template to:

<ng-container *ngFor="let name of names; let i = index;">
  <p *ngIf="nameStartsWithSList[i]">{{ name }}</p>
</ng-container>

but this has introduced a new list entirely to avoid the method call in the template. Is there a better way to go about doing this?


Solution

  • That's a very interesting question.

    One possible solution would be to pass the prefix and field to the directive and manipulate it accordingly. You could probably use the renderer2 as a better solution to present the paragraph with the field you want, but it was just to showcase it works.

    @Input() chars: string;
    @Input() field: string;
    
    constructor(private el: ElementRef) {}
    
    ngOnInit() {
      if (this.field.toLowerCase().includes(this.chars.toLowerCase())) {
        (this.el.nativeElement as HTMLElement).innerHTML = `<p>${this.field}</p>`;
      }
    }
    

    Another thing (which I actually just realized) is that you can use the directive as a component too.

    <ng-container *ngFor="let name of names">
      <showIfStartsWith chars="s" [field]="name"></showIfStartsWith>
    </ng-container>
    

    Full demo here.

    Edit: Found another solution less weird, without using the directive as a component. Demo V2

    Edit 2: Found another solution, using the directive as a structural directive, showcasing how do you pass multiple parameters to it. Demo V3