1

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;
}
adrisons
  • 3,443
  • 3
  • 32
  • 48

3 Answers3

1

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)

  • Thank you for your answer, as I say in the question `I am aware that using trackby improves efficiency`. But why does the code not work without `trackby`? – adrisons Oct 20 '21 at 14:13
1

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.

AT82
  • 71,416
  • 24
  • 140
  • 167
  • 1
    Thank you for your answer, I'll read that blog. Also, could you explain to me why the "infinite loop" only occurs when I use `ngFor` with `mat-option`? It has to be related, because the solution number 2 works fine. – adrisons Oct 21 '21 at 06:51
  • 1
    Interesting, I totally missed that part in your question. I'm not an expert when it comes to angular material, but maybe diving into the source code could help. Angular material uses a lot of observables under the hood, my best bet would be that is what causing this. If you use an observable in similar condition in your code, your browser would definitely crash. Angular material also mention that `The native control has several performance, accessibility, and usability advantages.` I'm sorry I can't be more specific on what causes thi, but as said,observables... 90% sure that it's the cause ;) – AT82 Oct 21 '21 at 08:54
0

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>
Andreas Rainer
  • 314
  • 4
  • 16
  • Thank you for your answer. What difference does it make to use public variable vs using get? Also, I understand how trackby works, my question is why is it needed when using mat-select. – adrisons Oct 20 '21 at 14:16