Reputation: 53
From reading several Angular resources (see below), calling methods in templates can cause performance issues. E.g.
<div>{{getDisplayName()}}</div>
However, quite a few of the Angular Material library examples call methods in the templates.
Tree
https://material.angular.io/components/tree/examples
<mat-icon class="mat-icon-rtl-mirror">
{{treeControl.isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
Table with Selection
https://material.angular.io/components/table/examples
<td mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)"
[aria-label]="checkboxLabel(row)">
</mat-checkbox>
</td>
These methods do return a value from a Set. E.g. https://github.com/angular/components/blob/4d4ee182de3f306c5fcf49853ef6ac71c4000eea/src/cdk/collections/selection.ts
isSelected(value: T): boolean {
return this._selection.has(value);
}
Will these methods cause performance issues just like *normal methods? Or is there something I am missing?
Resources
Does calling functions in templates cause performance issues in Angular2+?
Upvotes: 5
Views: 810
Reputation: 1884
The ultimately answer to the question, is yes - this will cause performance issues. And fortunately, it's actually easy to demonstrate, why, and how you might go about overcoming them.
For the purposes of illustration, I want you to copy a number of those mat-checkboxes, and insert them into your application somewhere.
Then, on the checkboxLabel method, I want you to put a console log at the very first line. Perform any interaction with the page, ideally check and uncheck one of the boxes.
Now, if you check your console, you might expect to see the checkboxLabel console log, appear once for every mat-checkbox. So if you put three in, you'd expect to see it, 3 times.
But if you followed those instructions exactly, you'll actually see the console log, many times depending on exactly what you kept in your test page and the set up of your project. And by many, we mean certainly 9+ times.
How Did This Happen?
Essentially, core to how Angular works, anytime there is a change in the DOM, every element with a method such as this has to be re-evaluated. It doesn't inherently cache the previous value and compare them, so it has to perform the calculation again, and return a result.
So if you had 20 divs with displayName() in them, that's many changes EVERY time there is a change. Which understandably causes a massive performance issue. When you think that most pages have much more than this displayed, you can see how people struggle to keep their applications performant, not because they're bad at coding - but because they might not understand exactly what's making it lag behind the scenes.
How Might You Overcome This?
Pipes.
If we instead convert our method into a pipe which can still utilize this method, we CAN take advantage of this caching method, and make our overall app performant.
Pipes will ONLY return a different result, and will only call their function, IF the input changes.
So for easiness sake: lets set up a dummy ts file, that has the following array.
labelExamples = [
{ row: '1' },
{ row: '1' },
{ row: '3' }
]
And an html file that has the following code:
<ng-container *ngFor="let l of labelExamples">
<div>{{checkboxLabel(l.row)}}</div>
</ng-container>
And finally, let's jump back into our ts file and say our checkboxLabel function looks like this:
checkboxLabel(row: string) {
switch (row) {
case '1':
console.log("hello");
return 'Row 1 Label';
default:
console.warn("hello from the other side");
return 'Any other row label'
}
}
(You can use this to test exactly what I've described as above.)
Well if you run this, you'll see exactly as I described, many instances of the console log "Hello" and "Hello from the other side".
But, if we change this to a pipe we could convert the above to the following
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'checkboxLabelPipe'
})
export class checkboxLabelPipe implements PipeTransform {
transform(row: string | string): string {
if (row) {
return this.checkboxLabel(row);
}
return row;
}
checkboxLabel(row: string): string {
switch(row) {
case '1':
console.log('Row 1 called');
return 'Row 1 Label';
default:
console.log('Any other row called');
return 'Any other row label'
}
}
}
Remember this is a pipe, and so it will have to be declared in an NgModule that it's used in, or the global NgModule.
If you were to run this instead, keeping the same html file, and the same array of labelExamples, you would see the exact same result as before except if you look in your console logs, you'll see closer to what you would originally expect. 3 console logs.
By understanding exactly when and how calculations are performed in Angular, and when best to use functions or pipes, you can greatly increase the performance of your entire application.
You can read up more on how Pipes go about achieving this, at the Angular Documentation. Angular Pipes Documentation
Additionally, for even greater performance boosts, you can look into the memo decorator. The memo-decorator npm can be found here. which caches already calculated results and saves you even more performance by returning results it has stored instead of even putting them through the process.
Upvotes: 3