简体   繁体   中英

Browser freezes when using mat-select with ngFor without trackBy

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

  1. 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>
  1. 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>
  1. It seems that in order to use 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;
}

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)

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>

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM