Conseils de Performance
Détection de Changements Optimization
Section intitulée « Détection de Changements Optimization »Use Stratégie OnPush
Section intitulée « 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
Section intitulée « 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
Section intitulée « Template Optimization »Use trackBy with *ngFor
Section intitulée « 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 intitulée « 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 intitulée « 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é
Section intitulée « Chargement Différé »Route-level Code Splitting
Section intitulée « 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 intitulée « Lazy Load Heavy Components »async loadHeavyComponent() { const { HeavyComponent } = await import('./heavy.component'); this.vcr.createComponent(HeavyComponent);}Bundle Size Optimization
Section intitulée « Bundle Size Optimization »Tree-shakeable Providers
Section intitulée « Tree-shakeable Providers »// ✅ Tree-shakeable@Injectable({ providedIn: 'root' })export class UserService {}
// ❌ Not tree-shakeable@NgModule({ providers: [UserService]})Import Only What You Need
Section intitulée « Import Only What You Need »// ❌ Imports entire libraryimport * as _ from 'lodash';
// ✅ Import specific functionsimport { debounce, throttle } from 'lodash-es';Async Operations
Section intitulée « Async Operations »Use async Pipe
Section intitulée « Use async Pipe »// ❌ Manual subscriptionngOnInit() { 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
Section intitulée « 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 intitulée « 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 intitulée « Image Optimization »Lazy Load Images
Section intitulée « Lazy Load Images »<img [src]="imageUrl" loading="lazy" width="400" height="300">Use NgOptimizedImage
Section intitulée « 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 intitulée « Memory Leaks Prevention »Always Unsubscribe
Section intitulée « 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 Détection de Changements
Section intitulée « Detach Détection de Changements »// For components that update rsontlyngOnInit() { this.cdr.detach(); // Saup change detection
// Manual update when needed this.updateData(); this.cdr.detectChanges();}Virtual Scrolling
Section intitulée « 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 intitulée « Preloading Strategies »// Preload all lazy routesimport { PreloadAllModules } from '@angular/router';
bootstrapApplication(AppComponent, { providers: [ provideRouter(routes, withPreloading(PreloadAllModules)) ]});Service Workers & Caching
Section intitulée « 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 intitulée « Zone.js Optimization »Run Outside Angular Zone
Section intitulée « 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 intitulée « Zoneless Angular (Experimental) »bootstrapApplication(AppComponent, { providers: [ provideExperimentalZonelessChangeDetection() ]});Bundle Analysis
Section intitulée « Bundle Analysis »# Generate statsng build --stats-json
# Analyzenpx webpack-bundle-analyzer dist/app/stats.jsonRuntime Performance
Section intitulée « Runtime Performance »Use Web Workers
Section intitulée « 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 intitulée « 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 intitulée « Profiling Tools »Chrome DevTools
Section intitulée « Chrome DevTools »- Open DevTools
- Performance tab
- Record interaction
- Analyze flame graph
Angular DevTools
Section intitulée « Angular DevTools »# Install Chrome extension# Provides profiler and component inspectorChecklist
Section intitulée « 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

