Reputation: 3723
TL;DR: I already have working solutions, but I would like explanations why mat-select behaves like this.
While building an application with Angular Material I run into this error when using mat-select
combined with *ngFor
in the template: the get priorities
function is being called continuously like in an infinite loop and the browser freezes.
get priorities() {
return [
{ name: 'NORMAL', value: 100 },
{ name: 'HIGH', value: 200 },
{ name: 'FORCE', value: 300 },
];
}
<mat-form-field appearance="outline">
<mat-label>Priority</mat-label>
<mat-select formControlName="priority">
<mat-option *ngFor="let element of priorities" [value]="element"
>{{ element.name }}</mat-option
>
</mat-select>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Priority</mat-label>
<mat-select formControlName="priority">
<mat-option [value]="priorities[0]">{{ priorities[0].name }}</mat-option>
<mat-option [value]="priorities[1]">{{ priorities[1].name }}</mat-option>
<mat-option [value]="priorities[2]">{{ priorities[2].name }}</mat-option>
</mat-select>
</mat-form-field>
<select matNativeControl>
<option *ngFor="let element of priorities" [value]="element">
{{ element.name }}
</option>
</select>
mat-option
with ngFor
we need to add a trackBy
function. ¿Can someone explain why? I am aware that using trackby
improves efficiency, but I have not found any Material documentation about why it is necessary to use it with mat-select
.<mat-form-field appearance="outline">
<mat-label>Priority</mat-label>
<mat-select formControlName="priority">
<mat-option
*ngFor="let element of priorities; trackBy: prioritiesTrackByFn"
[value]="element"
>{{ element.name }}</mat-option
>
</mat-select>
</mat-form-field>
prioritiesTrackByFn(index, item): number {
return item.value;
}
Upvotes: 1
Views: 2373
Reputation: 73377
This is how Angular change detection works, it has nothing to do with Angular material. Angular can run change detection for example when you click the page, when you type something in an input. Angular does not necessary know WHAT changed, so it checks everything, including your getter. And as your getter is called, the whole reference of the array is changed (you return a new array each time when getter is called), your array is re rendered and then Angular detects that the array changed, it will run change detection again.... So it kinda becomes a "loop" even though it technically isn't an infinite loop. Being an *ngFor, it just becomes worse.
This is worth a read regarding angular change detection: https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/
The simple solution here is to assign your array to a variable and iterate that array in your template instead of using the getter.
Upvotes: 1
Reputation: 344
Why use a getter? Have you tried to assign the priorities to a public variable in your .ts file?
And of course make use of trackBy:function, as described here: How to use `trackBy` with `ngFor`
like:
export class xy {
prios: PrioType[];
constructor() {
this.prios = getPrios();
}
trackById(index, item) {
return item.id;
}
}
<map-option *ngFor="let element of prios; trackBy:trackById">{{element.name}}</mat-option>
Upvotes: 1
Reputation: 2084
trackBy used for detecting changes in item and not redraw all items
trackBy hepl to update only 1 item, without trackBy angular will redraw all items (if var is array of objects)
Upvotes: 1