Внести вклад
Энциклопедия ошибок

Выражение отслеживания привело к пересозданию структуры DOM

Выражение отслеживания (track expression), указанное в цикле @for, привело к пересозданию DOM, соответствующего всем элементам. Это очень ресурсоемкая операция, которая часто возникает при работе с неизменяемыми (immutable) структурами данных. Например:

@Component({
  template: `
    <button (click)="toggleAllDone()">All done!</button>
    <ul>
    @for (todo of todos; track todo) {
      <li>{{todo.task}}</li>
    }
    </ul>
  `,
})
export class App {
  todos = [
    { id: 0, task: 'understand trackBy', done: false },
    { id: 1, task: 'use proper tracking expression', done: false },
  ];

  toggleAllDone() {
    this.todos = this.todos.map(todo => ({ ...todo, done: true }));
  }
}

В приведенном примере весь список со всеми представлениями (DOM-узлы, директивы Angular, компоненты, запросы и т.д.) пересоздается (!) после переключения статуса "done" у элементов. В данном случае было бы достаточно относительно «дешевого» обновления привязки свойства done.

Помимо значительного снижения производительности, пересоздание DOM-дерева приводит к потере состояния DOM-элементов ( например: фокуса, выделения текста, сайтов, загруженных в iframe, и т.д.).

Исправление ошибки

Измените выражение отслеживания так, чтобы оно уникально идентифицировало элемент в коллекции, независимо от идентичности самого объекта. В рассматриваемом примере правильное выражение track должно использовать уникальное свойство id (item.id):

@Component({
  template: `
    <button (click)="toggleAllDone()">All done!</button>
    <ul>
    @for (todo of todos; track todo.id) {
      <li>{{todo.task}}</li>
    }
    </ul>
  `,
})
export class App {
  todos = [
    { id: 0, task: 'understand trackBy', done: false },
    { id: 1, task: 'use proper tracking expression', done: false },
  ];

  toggleAllDone() {
    this.todos = this.todos.map(todo => ({ ...todo, done: true }));
  }
}