How to optimize Angular performance
Optimizing Angular performance involves change detection strategies, lazy loading, bundle optimization, and runtime performance improvements. As the creator of CoreUI with over 12 years of Angular experience since 2014, I’ve optimized numerous enterprise applications for production performance. Angular provides powerful optimization tools including OnPush change detection, lazy loading, and AOT compilation for faster applications. This approach ensures smooth user experience even in complex, data-heavy applications.
Use OnPush change detection, lazy loading, trackBy, AOT compilation, and bundle optimization to maximize Angular performance.
OnPush change detection:
// user-list.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core'
interface User {
id: number
name: string
email: string
}
@Component({
selector: 'app-user-list',
template: `
<div *ngFor="let user of users; trackBy: trackById" class="user">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserListComponent {
@Input() users: User[] = []
trackById(index: number, user: User): number {
return user.id
}
}
// parent.component.ts
@Component({
template: `<app-user-list [users]="users"></app-user-list>`
})
export class ParentComponent {
users: User[] = []
addUser(user: User): void {
// Create new array reference for OnPush detection
this.users = [...this.users, user]
}
}
Lazy loading modules:
// app-routing.module.ts
import { NgModule } from '@angular/core'
import { RouterModule, Routes } from '@angular/router'
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule)
},
{
path: 'users',
loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.module').then(m => m.SettingsModule)
}
]
@NgModule({
imports: [RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules // or SelectivePreloadingStrategy
})],
exports: [RouterModule]
})
export class AppRoutingModule {}
Virtual scrolling for large lists:
// virtual-scroll.component.ts
import { Component } from '@angular/core'
import { ScrollingModule } from '@angular/cdk/scrolling'
@Component({
selector: 'app-virtual-scroll',
standalone: true,
imports: [ScrollingModule],
template: `
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
<div *cdkVirtualFor="let item of items; trackBy: trackById" class="item">
{{ item.name }}
</div>
</cdk-virtual-scroll-viewport>
`,
styles: [`
.viewport {
height: 500px;
width: 100%;
}
.item {
height: 50px;
}
`]
})
export class VirtualScrollComponent {
items = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`
}))
trackById(index: number, item: any): number {
return item.id
}
}
Pure pipes for expensive computations:
// filter.pipe.ts
import { Pipe, PipeTransform } from '@angular/core'
@Pipe({
name: 'filter',
pure: true // Default, but explicit for clarity
})
export class FilterPipe implements PipeTransform {
transform(items: any[], searchTerm: string): any[] {
if (!items || !searchTerm) {
return items
}
return items.filter(item =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
)
}
}
// Usage in component
@Component({
template: `
<input [(ngModel)]="searchTerm" placeholder="Search">
<div *ngFor="let item of items | filter:searchTerm; trackBy: trackById">
{{ item.name }}
</div>
`
})
export class ListComponent {
items = []
searchTerm = ''
trackById(index: number, item: any): number {
return item.id
}
}
Detach change detection for heavy operations:
// heavy-computation.component.ts
import { Component, ChangeDetectorRef, OnDestroy } from '@angular/core'
@Component({
selector: 'app-heavy-computation',
template: `
<div>{{ result }}</div>
<button (click)="startComputation()">Start</button>
`
})
export class HeavyComputationComponent implements OnDestroy {
result = 0
constructor(private cdr: ChangeDetectorRef) {}
startComputation(): void {
// Detach change detection during heavy operation
this.cdr.detach()
this.performHeavyComputation().then(result => {
this.result = result
// Reattach and trigger detection
this.cdr.reattach()
this.cdr.detectChanges()
})
}
async performHeavyComputation(): Promise<number> {
// Simulate heavy computation
return new Promise(resolve => {
setTimeout(() => resolve(Math.random()), 2000)
})
}
ngOnDestroy(): void {
this.cdr.detach()
}
}
Production build optimization:
// angular.json
{
"projects": {
"app": {
"architect": {
"build": {
"configurations": {
"production": {
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]
}
}
}
}
}
}
}
Optimize observables:
// optimized.component.ts
import { Component, OnDestroy } from '@angular/core'
import { Subject, takeUntil } from 'rxjs'
@Component({
selector: 'app-optimized',
template: `<div>{{ data }}</div>`
})
export class OptimizedComponent implements OnDestroy {
data = ''
private destroy$ = new Subject<void>()
constructor(private dataService: DataService) {
// Unsubscribe automatically
this.dataService.getData()
.pipe(takeUntil(this.destroy$))
.subscribe(data => this.data = data)
}
ngOnDestroy(): void {
this.destroy$.next()
this.destroy$.complete()
}
}
Best Practice Note
Enable production mode with AOT compilation for smaller bundles and faster runtime. Use OnPush change detection on all components that receive immutable inputs. Implement trackBy for all ngFor loops. Lazy load feature modules to reduce initial bundle size. Use pure pipes for expensive transformations. Implement virtual scrolling for large lists. Detach change detection during heavy operations. Unsubscribe from observables in ngOnDestroy. This is how we optimize CoreUI Angular components—OnPush everywhere, lazy loading, trackBy on lists, and production builds reducing bundle size by 40-60% while improving runtime performance significantly.



