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
- Open DevTools
- Performance tab
- Record interaction
- 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