Performance Tips
Change Detection Optimization
Section titled “Change Detection Optimization”Use OnPush Strategy
Section titled “Use OnPush Strategy”import { Component, ChangeDetectionStrategy, input } from '@angular/core';
@Component({ selector: 'app-user-card', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, // Always! template: `<p>{{ user().name }}</p>`})export class UserCardComponent { user = input.required<User>();}Use Signals
Section titled “Use Signals”// ✅ Better performance with signals@Component({ changeDetection: ChangeDetectionStrategy.OnPush, template: `<p>Count: {{ count() }}</p>`})export class CounterComponent { count = signal(0);}
// ❌ Avoid getter functions@Component({ template: `<p>Total: {{ getTotal() }}</p>` // Runs every CD!})export class BadComponent { getTotal() { return this.items.reduce((sum, item) => sum + item.price, 0); }}
// ✅ Use computed instead@Component({ template: `<p>Total: {{ total() }}</p>`})export class GoodComponent { items = signal([...]); total = computed(() => this.items().reduce((sum, item) => sum + item.price, 0) );}Template Optimization
Section titled “Template Optimization”Use trackBy with *ngFor
Section titled “Use trackBy with *ngFor”// ❌ Without trackBy<div *ngFor="let item of items">{{ item.name }}</div>
// ✅ With trackBy<div *ngFor="let item of items; trackBy: trackById">{{ item.name }}</div>
trackById(index: number, item: any): number { return item.id;}Avoid Complex Template Expressions
Section titled “Avoid Complex Template Expressions”// ❌ Bad: Complex logic in template<div *ngIf="users.filter(u => u.active).length > 0"> {{ users.filter(u => u.active)[0].name }}</div>
// ✅ Good: Pre-calculate in componentactiveUsers = computed(() => this.users().filter(u => u.active));firstActiveUser = computed(() => this.activeUsers()[0]);
<div *ngIf="activeUsers().length > 0"> {{ firstActiveUser().name }}</div>Use Pure Pipes
Section titled “Use Pure Pipes”// ✅ Pure pipe (cached)@Pipe({ name: 'filter', pure: true })export class FilterPipe implements PipeTransform { transform(items: any[], filter: string): any[] { return items.filter(item => item.name.includes(filter)); }}
// ❌ Impure pipe (runs every CD)@Pipe({ name: 'filter', pure: false })Lazy Loading
Section titled “Lazy Loading”Route-level Code Splitting
Section titled “Route-level Code Splitting”export const routes: Routes = [ { path: 'admin', loadChildren: () => import('./admin/admin.routes') .then(m => m.ADMIN_ROUTES) }];Lazy Load Heavy Components
Section titled “Lazy Load Heavy Components”async loadHeavyComponent() { const { HeavyComponent } = await import('./heavy.component'); this.vcr.createComponent(HeavyComponent);}Bundle Size Optimization
Section titled “Bundle Size Optimization”Tree-shakeable Providers
Section titled “Tree-shakeable Providers”// ✅ Tree-shakeable@Injectable({ providedIn: 'root' })export class UserService {}
// ❌ Not tree-shakeable@NgModule({ providers: [UserService]})Import Only What You Need
Section titled “Import Only What You Need”// ❌ Imports entire libraryimport * as _ from 'lodash';
// ✅ Import specific functionsimport { debounce, throttle } from 'lodash-es';Async Operations
Section titled “Async Operations”Use async Pipe
Section titled “Use async Pipe”// ❌ Manual subscriptionngOnInit() { this.userService.getUsers().subscribe(users => { this.users = users; });}
// ✅ Async pipe (auto-unsubscribe)users$ = this.userService.getUsers();<div *ngFor="let user of users$ | async">{{ user.name }}</div>Debounce User Input
Section titled “Debounce User Input”searchTerm$ = new Subject<string>();
ngOnInit() { this.searchTerm$.pipe( debounceTime(300), distinctUntilChanged(), switchMap(term => this.searchService.search(term)) ).subscribe(results => this.results = results);}Use switchMap for Cancellation
Section titled “Use switchMap for Cancellation”// ✅ Cancels previous requestthis.searchTerm$.pipe( switchMap(term => this.http.get(`/search?q=${term}`))).subscribe();
// ❌ All requests complete (memory leak!)this.searchTerm$.pipe( mergeMap(term => this.http.get(`/search?q=${term}`))).subscribe();Image Optimization
Section titled “Image Optimization”Lazy Load Images
Section titled “Lazy Load Images”<img [src]="imageUrl" loading="lazy" width="400" height="300">Use NgOptimizedImage
Section titled “Use NgOptimizedImage”import { NgOptimizedImage } from '@angular/common';
@Component({ imports: [NgOptimizedImage], template: ` <img ngSrc="hero.jpg" width="400" height="300" priority> <!-- Above-the-fold images --> `})Memory Leaks Prevention
Section titled “Memory Leaks Prevention”Always Unsubscribe
Section titled “Always Unsubscribe”// ✅ Using takeUntilprivate destroy$ = new Subject<void>();
ngOnInit() { this.dataService.getData().pipe( takeUntil(this.destroy$) ).subscribe();}
ngOnDestroy() { this.destroy$.next(); this.destroy$.complete();}Detach Change Detection
Section titled “Detach Change Detection”// For components that update rarelyngOnInit() { this.cdr.detach(); // Stop change detection
// Manual update when needed this.updateData(); this.cdr.detectChanges();}Virtual Scrolling
Section titled “Virtual Scrolling”import { ScrollingModule } from '@angular/cdk/scrolling';
@Component({ imports: [ScrollingModule], template: ` <cdk-virtual-scroll-viewport itemSize="50" style="height: 400px"> <div *cdkVirtualFor="let item of items"> {{ item.name }} </div> </cdk-virtual-scroll-viewport> `})export class VirtualListComponent { items = Array.from({ length: 10000 }, (_, i) => ({ name: `Item ${i}` }));}Preloading Strategies
Section titled “Preloading Strategies”// Preload all lazy routesimport { PreloadAllModules } from '@angular/router';
bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withPreloading(PreloadAllModules)) ]});Service Workers & Caching
Section titled “Service Workers & Caching”ng add @angular/pwa// Configure caching in ngsw-config.json{ "dataGroups": [{ "name": "api", "urls": ["/api/**"], "cacheConfig": { "maxAge": "1h", "strategy": "freshness" } }]}Zone.js Optimization
Section titled “Zone.js Optimization”Run Outside Angular Zone
Section titled “Run Outside Angular Zone”constructor(private ngZone: NgZone) {}
ngOnInit() { // Heavy operations outside Angular zone this.ngZone.runOutsideAngular(() => { setInterval(() => { this.updateChart(); // No CD triggered }, 100); });}Zoneless Angular (Experimental)
Section titled “Zoneless Angular (Experimental)”bootstrapApplication(AppComponent, { providers: [ provideExperimentalZonelessChangeDetection() ]});Bundle Analysis
Section titled “Bundle Analysis”# Generate statsng build --stats-json
# Analyzenpx webpack-bundle-analyzer dist/app/stats.jsonRuntime Performance
Section titled “Runtime Performance”Use Web Workers
Section titled “Use Web Workers”ng generate web-worker appconst worker = new Worker(new URL('./app.worker', import.meta.url));worker.onmessage = ({ data }) => { console.log('Result:', data);};worker.postMessage({ data: heavyData });Memoization
Section titled “Memoization”const cache = new Map();
getData(id: string) { if (cache.has(id)) { return of(cache.get(id)); }
return this.http.get(`/api/data/${id}`).pipe( tap(data => cache.set(id, data)), shareReplay(1) );}Profiling Tools
Section titled “Profiling Tools”Chrome DevTools
Section titled “Chrome DevTools”- Open DevTools
- Performance tab
- Record interaction
- Analyze flame graph
Angular DevTools
Section titled “Angular DevTools”# Install Chrome extension# Provides profiler and component inspectorChecklist
Section titled “Checklist”- Use OnPush change detection
- Use signals for state
- Add trackBy to *ngFor
- Use async pipe
- Lazy load routes
- Optimize images
- Unsubscribe from observables
- Use virtual scrolling for long lists
- Enable production mode
- Use AOT compilation
- Analyze bundle size
- Use service workers
- Debounce user input
- Avoid complex template expressions
- Use pure pipes

