Выражение отслеживания (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 }));
}
}