Что такое trackBy в ngFor и зачем он нужен?
В Angular директива *ngFor используется для отображения списков, создавая DOM-элементы на основе массива данных. Однако при обновлении массива Angular по умолчанию использует сравнение по ссылке для определения изменений, что может приводить к неэффективному обновлению DOM: элементы удаляются и создаются заново, даже если фактически изменилось только одно поле объекта.
Чтобы минимизировать лишние перерисовки и повысить производительность, Angular предоставляет механизм trackBy, позволяющий явно указать, как отслеживать уникальность элементов списка.
1. Как работает *ngFor без trackBy
Пример:
<li \*ngFor="let item of items">{{ item.name }}</li>
Если массив items обновится (например, перезапишется новым, но с теми же значениями), Angular:
-
удалит все старые DOM-элементы;
-
создаст новые элементы с нуля;
-
при этом все связанные события и состояния будут потеряны.
Причина в том, что Angular по умолчанию отслеживает элементы по ссылке на объект (reference equality). Если ссылка изменилась — считается, что объект новый.
2. Что делает trackBy
trackBy — это функция, возвращающая уникальный идентификатор элемента. Angular использует это значение, чтобы понять, какие элементы были изменены, добавлены или удалены.
Сигнатура функции:
trackByFn(index: number, item: any): any
-
index — порядковый номер элемента в массиве;
-
item — сам объект;
-
возвращаемое значение — уникальный идентификатор (обычно id).
3. Пример использования trackBy
<li \*ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
trackById(index: number, item: any): number {
return item.id;
}
Если у объекта есть поле id, которое остаётся стабильным между обновлениями, Angular сможет корректно сопоставить DOM-элементы и не пересоздавать их.
4. Польза от trackBy
4.1 Производительность
-
Уменьшается количество операций с DOM.
-
Angular не пересоздаёт компонент, если trackBy говорит, что объект тот же.
-
Особенно актуально при большом количестве элементов или частом обновлении списка.
4.2 Сохранение состояния
-
DOM-элементы сохраняют внутреннее состояние: фокус, ввод, scroll и др.
-
Например, если пользователь печатает в input, а список обновляется — без trackBy фокус и ввод будут теряться.
4.3 Предсказуемость и контроль
-
Вы явно задаёте стратегию сопоставления элементов.
-
Это особенно важно при анимациях, переносе элементов и сложной логике.
5. Пример проблемы без trackBy
this.items = \[...this.items\];
Это перезаписывает массив новой ссылкой, даже если элементы идентичны. Angular:
-
считает, что весь список другой;
-
удаляет все старые DOM-элементы;
-
создаёт новые, полностью теряя все состояния.
6. Полный пример
items = \[
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
\];
trackByItemId(index: number, item: any): number {
return item.id;
}
<div \*ngFor="let item of items; trackBy: trackByItemId">
{{ item.name }}
</div>
При любом обновлении, если id остаётся прежним, DOM-элемент сохраняется.
7. Когда trackBy обязателен
-
Работа с большими массивами (>100 элементов).
-
Обновление по WebSocket или через polling.
-
Списки с полями ввода (input, textarea, select).
-
Анимации и перемещение элементов (@angular/animations).
-
Частые мутации: splice, reverse, sort.
8. Пример с FormArray и FormGroup
<div \*ngFor="let group of formArray.controls; trackBy: trackByIndex">
<input \[formControl\]="group.get('name')" />
</div>
trackByIndex(index: number, \_: any): number {
return index;
}
Если нет id, можно отслеживать по индексу, хотя это менее надёжно при сортировке.
9. Ошибки при неправильной реализации
9.1 Повторяющиеся идентификаторы
Если trackBy возвращает одинаковое значение для разных объектов, Angular будет считать их одинаковыми — возможны баги.
9.2 undefined или null
Если trackBy возвращает undefined, Angular будет вести себя как без trackBy.
9.3 Перепутаны аргументы
Некоторые разработчики ошибочно делают trackBy(item, index), но порядок должен быть (index, item).
10. Использование без trackBy
Это допустимо, но:
-
Angular теряет производительность;
-
Возникают лишние пересоздания;
-
Поведение может быть нестабильным при частых обновлениях.
11. Советы
-
Всегда использовать trackBy, если у элементов есть уникальный id.
-
Если id нет — использовать индекс, но избегать сортировок и удаления, иначе возможны визуальные артефакты.
-
trackBy особенно важен в virtual-scroll, cdkFor, списках с @angular/animations.
Таким образом, trackBy — это механизм оптимизации рендеринга списков в Angular, позволяющий точно отслеживать элементы массива по уникальному идентификатору, избегая ненужных перерисовок и потерь состояний при обновлении данных.