How to use trackBy with ngFor in Angular
Using trackBy with ngFor in Angular dramatically improves list rendering performance by tracking items by unique identifier instead of object reference. As the creator of CoreUI with over 12 years of Angular experience since 2014, I’ve optimized countless lists with trackBy in enterprise applications. Angular’s trackBy function prevents unnecessary DOM manipulation when list items change, reducing re-renders and improving application responsiveness. This approach is essential for lists that update frequently or contain complex templates.
Implement trackBy function with ngFor to track list items by unique identifier and prevent unnecessary re-renders.
Basic trackBy usage:
// user-list.component.ts
import { Component } from '@angular/core'
interface User {
id: number
name: string
email: string
}
@Component({
selector: 'app-user-list',
template: `
<div class="user-list">
<div *ngFor="let user of users; trackBy: trackByUserId" class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
</div>
`
})
export class UserListComponent {
users: User[] = [
{ id: 1, name: 'John Doe', email: '[email protected]' },
{ id: 2, name: 'Jane Smith', email: '[email protected]' },
{ id: 3, name: 'Bob Johnson', email: '[email protected]' }
]
trackByUserId(index: number, user: User): number {
return user.id
}
refreshUsers(): void {
// Fetch new data - items with same ID won't re-render
this.users = [
{ id: 1, name: 'John Doe Updated', email: '[email protected]' },
{ id: 2, name: 'Jane Smith', email: '[email protected]' },
{ id: 4, name: 'New User', email: '[email protected]' }
]
}
}
Performance comparison:
// without-trackby.component.ts
@Component({
selector: 'app-without-trackby',
template: `
<div *ngFor="let item of items" class="item">
{{ item.name }}
</div>
`
})
export class WithoutTrackByComponent {
items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
updateItems(): void {
// Without trackBy, ALL DOM elements are recreated
this.items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
}
}
// with-trackby.component.ts
@Component({
selector: 'app-with-trackby',
template: `
<div *ngFor="let item of items; trackBy: trackById" class="item">
{{ item.name }}
</div>
`
})
export class WithTrackByComponent {
items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
trackById(index: number, item: any): number {
return item.id
}
updateItems(): void {
// With trackBy, only changed items are updated
this.items = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
}
}
Advanced trackBy patterns:
// product-list.component.ts
import { Component, OnInit } from '@angular/core'
interface Product {
id: string
name: string
price: number
category: string
}
@Component({
selector: 'app-product-list',
template: `
<div class="products">
<div *ngFor="let product of products; trackBy: trackByProductId; let i = index"
class="product-card">
<span class="index">{{ i + 1 }}</span>
<h3>{{ product.name }}</h3>
<p>{{ product.price | currency }}</p>
<span class="category">{{ product.category }}</span>
</div>
</div>
`
})
export class ProductListComponent implements OnInit {
products: Product[] = []
ngOnInit(): void {
this.loadProducts()
}
trackByProductId(index: number, product: Product): string {
return product.id
}
loadProducts(): void {
this.products = [
{ id: 'p1', name: 'Laptop', price: 999, category: 'Electronics' },
{ id: 'p2', name: 'Mouse', price: 29, category: 'Accessories' },
{ id: 'p3', name: 'Keyboard', price: 79, category: 'Accessories' }
]
}
sortByPrice(): void {
// Items maintain DOM elements when sorted
this.products = [...this.products].sort((a, b) => a.price - b.price)
}
filterByCategory(category: string): void {
// Only removed items have DOM elements destroyed
this.products = this.products.filter(p => p.category === category)
}
}
trackBy with complex objects:
// order-list.component.ts
interface Order {
orderId: string
customer: {
id: number
name: string
}
items: Array<{ productId: string; quantity: number }>
total: number
}
@Component({
selector: 'app-order-list',
template: `
<div *ngFor="let order of orders; trackBy: trackByOrderId" class="order">
<h3>Order #{{ order.orderId }}</h3>
<p>Customer: {{ order.customer.name }}</p>
<div *ngFor="let item of order.items; trackBy: trackByProductId" class="item">
Product: {{ item.productId }} - Qty: {{ item.quantity }}
</div>
<p>Total: {{ order.total | currency }}</p>
</div>
`
})
export class OrderListComponent {
orders: Order[] = []
trackByOrderId(index: number, order: Order): string {
return order.orderId
}
trackByProductId(index: number, item: any): string {
return item.productId
}
}
Best Practice Note
Always use trackBy with ngFor for lists that change frequently. Track by unique identifier (id), not by index, for stable tracking. Use simple primitive values (number, string) as tracking keys for best performance. Avoid complex calculations in trackBy function—it runs frequently. TrackBy prevents Angular from destroying and recreating DOM elements unnecessarily. This is how we optimize lists in CoreUI Angular components—trackBy for all dynamic lists, reducing re-renders by up to 90% and significantly improving application performance.



