Aller au contenu principal

Conseils de Performance

Détection de Changements Optimization

Use Stratégie OnPush

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

// ✅ 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);
}
}

// ✅ Utiliser 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

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

// ❌ 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 component
activeUsers = computed(() => this.users().filter(u => u.active));
firstActiveUser = computed(() => this.activeUsers()[0]);

<div *ngIf="activeUsers().length > 0">
{{ firstActiveUser().name }}
</div>

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 })

Chargement Différé

Route-level Code Splitting

export const routes: Routes = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes')
.then(m => m.ADMIN_ROUTES)
}
];

Lazy Load Heavy Components

async loadHeavyComponent() {
const { HeavyComponent } = await import('./heavy.component');
this.vcr.createComponent(HeavyComponent);
}

Bundle Size Optimization

Tree-shakeable Providers

// ✅ Tree-shakeable
@Injectable({ providedIn: 'root' })
export class UserService {}

// ❌ Not tree-shakeable
@NgModule({
providers: [UserService]
})

Import Only What You Need

// ❌ Imports entire library
import * as _ from 'lodash';

// ✅ Import specific functions
import { debounce, throttle } from 'lodash-es';

Async Operations

Use async Pipe

// ❌ Manual subscription
ngOnInit() {
this.userService.getUsers().subscribe(users => {
this.users = users;
});
}

// ✅ Async pipe (auau-unsubscribe)
users$ = this.userService.getUsers();
<div *ngFor="let user of users$ | async">{{ user.name }}</div>

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

// ✅ Cancels previous request
this.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

Lazy Load Images

<img
[src]="imageUrl"
loading="lazy"
width="400"
height="300">

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

Always Unsubscribe

// ✅ Using takeUntil
private destroy$ = new Subject<void>();

ngOnInit() {
this.dataService.getData().pipe(
takeUntil(this.destroy$)
).subscribe();
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}

Detach Détection de Changements

// For components that update rsontly
ngOnInit() {
this.cdr.detach(); // Saup change detection

// Manual update when needed
this.updateData();
this.cdr.detectChanges();
}

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

// Preload all lazy routes
import { PreloadAllModules } from '@angular/router';

bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes, withPreloading(PreloadAllModules))
]
});

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

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)

// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideExperimentalZonelessChangeDetection()
]
});

Bundle Analysis

# Generate stats
ng build --stats-json

# Analyze
npx webpack-bundle-analyzer dist/app/stats.json

Runtime Performance

Use Web Workers

ng generate web-worker app
const worker = new Worker(new URL('./app.worker', import.meta.url));
worker.onmessage = ({ data }) => {
console.log('Result:', data);
};
worker.postMessage({ data: heavyData });

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

Chrome DevTools

  1. Open DevTools
  2. Performance tab
  3. Record interaction
  4. Analyze flame graph

Angular DevTools

# Install Chrome extension
# Provides profiler and component inspector

Checklist

  • Utiliser OnPush change detection
  • Utiliser signals for state
  • Ajouter trackBy à *ngFor
  • Utiliser async pipe
  • Lazy load routes
  • Optimize images
  • Se désabonner des observables
  • Utiliser virtual scrolling for long lists
  • Enable production mode
  • Utiliser AOT compilation
  • Analyze bundle size
  • Utiliser service workers
  • Debounce user input
  • Avoid complex template expressions
  • Utiliser pure pipes