Skip to content

trackBy / track

Without tracking, Angular destroys and recreates every DOM element in a list whenever the data changes. track (and the legacy trackBy) tells Angular how to identify each item so it only touches what actually changed.

Modern Control Flow (@for — Angular 17+)

Section titled “Modern Control Flow (@for — Angular 17+)”

track is required in @for — Angular enforces the best practice at compile time:

@for (album of albums; track album.id) {
<app-album-card [album]="album" />
}
  • track album.id — use a stable, unique property
  • track $index — valid but less efficient (DOM is recycled by position, not identity)

For Angular 16 and below, or when still using *ngFor:

@Component({
template: `
<div *ngFor="let album of albums; trackBy: trackById">
{{ album.title }}
</div>
`
})
export class AlbumListComponent {
trackById(index: number, album: Album): number {
return album.id;
}
}

The trackBy function receives (index, item) and must return a stable unique value — usually the item’s ID.

Scenario Without track With track
API re-fetches same list All DOM nodes destroyed and recreated Nothing changes
One item added to end All nodes recreated One node inserted
One item changes in middle All nodes recreated Only that node updated
Items reordered All nodes recreated Nodes moved in place

The performance gain is significant for long lists or lists that update frequently.

<!-- ✅ Stable unique ID -->
@for (item of items; track item.id) { ... }
<!-- ✅ Compound key if no single unique field -->
@for (item of items; track item.type + '-' + item.id) { ... }
<!-- ⚠️ Index only — safe for static lists, poor for dynamic -->
@for (item of items; track $index) { ... }
<!-- ❌ Tracking an unstable value defeats the purpose -->
@for (item of items; track item) { ... }

In modern Angular, @for with track replaces *ngFor + trackBy. The explicit track expression is shorter, inlined, and required — you cannot forget it.