Pourquoi Optimiser les performances Angular ?
Dans un environnement moderne et concurrentiel, la performance est devenue une priorité cruciale pour les développeurs. Un site ou une application qui ne répond pas rapidement peut perdre son utilisateur au profit d'un autre plus rapide. L'optimisation des performances en Angular permet non seulement d'améliorer l'expérience utilisateur mais aussi de réduire le temps de chargement et la consommation de ressources du navigateur.
Un cas concret serait une application de gestion de projet où chaque seconde compte pour les responsables qui doivent prendre des décisions rapidement. Optimiser les performances permettrait d'améliorer la productivité globale de l'équipe en offrant des réponses instantanées aux actions des utilisateurs.
Prerequis
- Connaissance approfondie de Angular 10+.
- Familiarité avec TypeScript et JavaScript ES6+.
- Capacité à travailler avec les outils de développement Angular tels que Angular CLI, Webpack, etc.
- Un environnement de développement propre (Node.js, npm).
Concepts fondamentaux
1. Lazy Loading
Le lazy loading est une technique permettant d'éviter le chargement inutile des composants et des modules non nécessaires lors du démarrage de l'application. Cela réduit la taille initiale de l'application et accélère le temps de chargement.
Schéma mental :
graph TD;
A[Application] -->|Lazy Loading| B[Module 1];
A -->|Lazy Loading| C[Module 2];
B --> D[Component 1];
B --> E[Component 2];
C --> F[Component 3];
Code fonctionnel :
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Module1Component } from './module1/module1.component';
import { Module2Component } from './module2/module2.component';
const routes: Routes = [
{ path: 'module1', loadChildren: () => import('./module1/module1.module').then(m => m.Module1Module) },
{ path: 'module2', loadChildren: () => import('./module2/module2.module').then(m => m.Module2Module) }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// module1.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Module1Component } from './module1.component';
@NgModule({
declarations: [Module1Component],
imports: [CommonModule]
})
export class Module1Module { }
2. OnPush Change Detection Strategy
Angular utilise le changement détection par défaut pour vérifier toutes les propriétés d'un composant à chaque itération du cycle de vie. Cependant, dans certains cas, cela peut entraîner une performance dégradée. L'OnPush change detection strategy permet de restreindre la vérification aux valeurs qui ont réellement changé.
Schéma mental :
graph TD;
A[Component] --> B[Prop1];
A --> C[Prop2];
Code fonctionnel :
// app.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {
prop1 = 'Value 1';
prop2 = 'Value 2';
}
3. AOT Compilation
Angular Off-The-Shelf (AOT) est une technique qui compile le code TypeScript en JavaScript avant de l'exécuter dans le navigateur. Cela réduit la taille du bundle final et accélère le temps de démarrage.
Schéma mental :
graph TD;
A[TypeScript] --> B[AOT Compilation];
B --> C[JavaScript];
Code fonctionnel :
## Terminal command to build Angular app with AOT
ng build --prod
4. Virtual Scrolling
La virtual scrolling est une technique permettant de rendre des listes ou des tableaux très longs plus performants en affichant uniquement les éléments visibles sur l'écran.
Schéma mental :
graph TD;
A[List] --> B[Visible Items];
Code fonctionnel :
// app.component.html
<div *ngFor="let item of items | slice: 0:visibleItems; let i = index">
item
</div>
// app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
items = Array.from({ length: 1000 }, (_, i) => `Item ${i}`);
visibleItems = 20;
}
Mise en pratique : projet fil rouge
Étape 1 : Création du projet
## Terminal command to create Angular project
ng new task-manager-app --strict
cd task-manager-app
Étape 2 : Configuration du lazy loading
Créez deux modules dashboard et tasks.
## Terminal commands to generate modules and components
ng generate module dashboard --route=dashboard
ng generate component dashboard/home
ng generate module tasks --route=tasks
ng generate component tasks/list
Étape 3 : Configuration des routes
Mettez à jour le fichier app-routing.module.ts.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { TasksComponent } from './tasks/tasks.component';
const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'dashboard', loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule) },
{ path: 'tasks', loadChildren: () => import('./tasks/tasks.module').then(m => m.TasksModule) }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Étape 4 : Création des composants
Mettez à jour les fichiers dashboard/home.component.ts et tasks/list.component.ts.
// dashboard/home.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent {
title = 'Welcome to the Task Manager Dashboard';
}
angular
// tasks/list.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
tasks = [
{ id: 1, title: 'Task 1', description: 'Description of Task 1' },
{ id: 2, title: 'Task 2', description: 'Description of Task 2' }
// Add more tasks as needed
];
constructor() { }
ngOnInit(): void {
}
}
Étape 5 : Configuration des modules
Mettez à jour les fichiers dashboard/dashboard.module.ts et tasks/tasks.module.ts.
// dashboard/dashboard.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DashboardComponent } from './dashboard.component';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: '', component: HomeComponent }
];
@NgModule({
declarations: [DashboardComponent],
imports: [CommonModule, RouterModule.forChild(routes)]
})
export class DashboardModule { }
angular
// tasks/tasks.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TasksComponent } from './tasks.component';
import { RouterModule, Routes } from '@angular/router';
const routes: Routes = [
{ path: '', component: ListComponent }
];
@NgModule({
declarations: [TasksComponent],
imports: [CommonModule, RouterModule.forChild(routes)]
})
export class TasksModule { }
Étape 6 : Ajout des composants au template
Mettez à jour les fichiers app.component.html.
// app.component.html
<nav>
<a routerLink="/dashboard">Dashboard</a> |
<a routerLink="/tasks">Tasks</a>
</nav>
<router-outlet></router-outlet>
Étape 7 : Exécution du projet
## Terminal command to serve Angular app
ng serve
Erreurs frequentes et debugging
1. ERROR: Cannot read property 'subscribe' of undefined
Ce message d'erreur peut se produire lorsque vous essayez de vous abonner à un observable qui n'est pas défini.
Code incorrect :
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
data: any;
constructor(private dataService: DataService) { }
ngOnInit(): void {
this.dataService.getData().subscribe(data => {
this.data = data;
});
}
}
Code correct :
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from './data.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
data: any;
constructor(private dataService: DataService) { }
ngOnInit(): void {
if (this.dataService.getData()) {
this.dataService.getData().subscribe(data => {
this.data = data;
});
}
}
}
2. ERROR: Cannot find module './non-existent-module'
Ce message d'erreur indique que le module spécifié n'existe pas.
Code incorrect :
## Terminal command to generate a new component
ng generate component non-existent-module
Correction :
Assurez-vous de saisir le nom du module correctement.
3. ERROR: Expression has changed after it was checked
Ce message d'erreur peut se produire lorsque vous essayez de modifier une propriété après que le changement détecté ait été vérifié.
Code incorrect :
// app.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'App';
ngOnInit(): void {
setTimeout(() => {
this.title = 'Updated Title';
}, 1000);
}
}
Code correct :
Utilisez ChangeDetectorRef pour forcer la mise à jour du DOM.
// app.component.ts
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'App';
constructor(private cdr: ChangeDetectorRef) { }
ngOnInit(): void {
setTimeout(() => {
this.title = 'Updated Title';
this.cdr.detectChanges();
}, 1000);
}
}
Pour aller plus loin
Angular Universal : Apprenez à rendre votre application Angular universelle pour une meilleure performances sur les moteurs de recherche et les devices mobiles.
RxJS Best Practices : Explorez des meilleures pratiques pour utiliser RxJS, la bibliothèque reactive JavaScript utilisée par Angular.
AOT Compilation vs JIT Compilation : Comprendre les différences et les avantages de chaque type de compilation pour optimiser vos projets Angular.
Défi pratique
Définissez une fonction debounce personnalisée en TypeScript qui prend un temps d'attente et une fonction à débouncer, puis l'exécute après le délai spécifié.
function debounce<T extends (...args: any[]) => void>(func: T, wait: number): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
return function(...args: Parameters<T>): void {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
func.apply(this, args);
}, wait);
};
}
Testez cette fonction avec un exemple de recherche en temps réel :
const searchInput = document.getElementById('search-input') as HTMLInputElement;
function handleSearch(query: string): void {
console.log(`Searching for: ${query}`);
}
const debouncedSearch = debounce(handleSearch, 300);
searchInput.addEventListener('input', (event) => {
const query = (event.target as HTMLInputElement).value;
debouncedSearch(query);
});
Cela permet d'éviter les appels fréquents à la fonction de recherche et d'améliorer ainsi les performances.