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.
Component
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>
Posible solutions
- Specifying each mat-option (without ngFor). Although this is not useful for many cases.
<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>
- Using native html select and option. This is a valid solution, but I wanted to make it work with Material.
<select matNativeControl>
<option *ngFor="let element of priorities" [value]="element">
{{ element.name }}
</option>
</select>
- It seems that in order to use
mat-option
withngFor
we need to add atrackBy
function. ¿Can someone explain why? I am aware that usingtrackby
improves efficiency, but I have not found any Material documentation about why it is necessary to use it withmat-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;
}
CodePudding user response:
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)
CodePudding user response:
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>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>
CodePudding user response:
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.