Skip to main 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+)

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)

Legacy *ngFor with trackBy

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.

Why It Matters

ScenarioWithout trackWith track
API re-fetches same listAll DOM nodes destroyed and recreatedNothing changes
One item added to endAll nodes recreatedOne node inserted
One item changes in middleAll nodes recreatedOnly that node updated
Items reorderedAll nodes recreatedNodes moved in place

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

What to Track

<!-- ✅ 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) { ... }

Key Point

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