A expressão de tracking de identidade especificada no loop @for causou recriação do DOM correspondente a todos os itens. Esta é uma operação muito custosa que comumente ocorre ao trabalhar com estruturas de dados imutáveis. Por exemplo:
@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 })); }}
No exemplo fornecido, a lista inteira com todas as views (nós DOM, diretivas Angular, components, queries, etc.) são recriadas (!) após alternar o status "done" dos itens. Aqui, uma atualização de binding relativamente barata para a propriedade done seria suficiente.
Além de ter uma alta penalidade de performance, recriar a árvore DOM resulta em perda de estado nos elementos DOM (ex.: foco, seleção de texto, sites carregados em um iframe, etc.).
Corrigindo o erro
Altere a expressão de tracking de forma que ela identifique uniquamente um item em uma coleção, independentemente de sua identidade de objeto. No exemplo discutido, a expressão track correta usaria a propriedade única 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 })); }}