Appearance
Maîtriser les Preloading Strategies
WARNING
Pour ce suivre cette article, vous devez d'abord connaître la notion de lazy-loading. Si ce n'est pas le cas, lisez notre article sur le lazy-loading.
Imaginez que vous gérez un grand magasin en ligne. Au lieu d'attendre que les clients cliquent sur une catégorie pour charger ses produits, vous pourriez anticiper leurs besoins et commencer à charger certaines catégories populaires en arrière-plan. C'est exactement ce que font les Preloading Strategies dans Angular : elles anticipent les besoins de l'utilisateur en chargeant certains modules à l'avance.
Commençons par un exemple simple. Supposons que nous ayons une application de gestion d'utilisateurs avec plusieurs routes chargées paresseusement (lazy-loaded).
typescript
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'users',
loadComponent: () => import('./components/features/user/user.component').then(m => m.UserComponent)
},
{
path: 'admin',
loadComponent: () => import('./components/features/admin/admin.component').then(m => m.AdminComponent)
}
];
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withPreloading(PreloadAllModules)),
provideHttpClient()
]
};
Le "chunk" est chargé même si l'utilisateur ne navigue pas vers cette route.
Examinons de plus près ce que fait loadComponent
et comment la stratégie PreloadAllModules
affecte le chargement :
loadComponent
: Cette fonction est utilisée pour le chargement paresseux (lazy loading) des composants. Elle permet de charger un composant uniquement lorsque sa route est activée, plutôt que de le charger au démarrage de l'application.typescriptloadComponent: () => import('./components/features/user/user.component').then(m => m.UserComponent)
- Cette ligne utilise une importation dynamique pour charger le fichier du composant.
- Le composant n'est chargé que lorsque l'utilisateur navigue vers la route correspondante.
- Cela permet de réduire le temps de chargement initial de l'application en ne chargeant que les composants nécessaires.
Stratégie
PreloadAllModules
: Cette stratégie, utilisée avecwithPreloading
dansapp.config.ts
, modifie le comportement du chargement paresseux :typescriptprovideRouter(routes, withPreloading(PreloadAllModules))
- Sans préchargement, les composants ne seraient chargés que lorsque l'utilisateur navigue vers leur route.
- Avec
PreloadAllModules
, Angular commence à charger tous les composants lazy-loaded en arrière-plan dès que l'application initiale est chargée. - Cela signifie que même si
UserComponent
etAdminComponent
sont définis pour un chargement paresseux, ils seront préchargés en arrière-plan. - L'avantage est que lorsque l'utilisateur navigue vers ces routes, les composants sont déjà chargés, offrant une navigation plus rapide.
CONSEIL
La stratégie PreloadAllModules
est particulièrement utile pour les petites à moyennes applications où vous voulez combiner les avantages du chargement initial rapide (grâce au lazy loading) et de la navigation rapide ultérieure (grâce au préchargement).
ATTENTION
Bien que le préchargement avec PreloadAllModules
puisse améliorer les performances de navigation, il augmente la quantité de données téléchargées initialement. Pour les grandes applications ou les connexions lentes, cela pourrait ne pas être la meilleure approche.
Créer une stratégie de préchargement personnalisée
Parfois, les stratégies de préchargement par défaut d'Angular ne suffisent pas pour répondre aux besoins spécifiques de votre application. C'est là qu'interviennent les stratégies de préchargement personnalisées.
Imaginons un scénario concret : vous avez une application de gestion d'utilisateurs avec un espace administrateur. Vous voulez précharger le module administrateur uniquement pour les utilisateurs connectés ayant des droits d'administrateur.
Voici comment nous pourrions implémenter cette stratégie :
typescript
import { Injectable, inject } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
// On part du principe que le service existe déjà
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class AdminPreloadingStrategy implements PreloadingStrategy {
private userService = inject(UserService);
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.path === 'admin' && this.userService.isAdmin()) {
return load();
} else {
return of(null);
}
}
}
typescript
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'users',
loadComponent: () => import('./components/features/user/user.component').then(m => m.UserComponent)
},
{
path: 'admin',
loadComponent: () => import('./components/features/admin/admin.component').then(m => m.AdminComponent)
}
];
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
import { AdminPreloadingStrategy } from './custom-preloading-strategy';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withPreloading(AdminPreloadingStrategy)),
provideHttpClient()
]
};
Examinons ce que fait notre stratégie personnalisée :
Nous créons une classe
AdminPreloadingStrategy
qui implémente l'interfacePreloadingStrategy
.Dans la méthode
preload
, nous vérifions deux conditions :- Si la route en cours de traitement est 'admin'
- Si l'utilisateur actuel est un administrateur (via
this.userService.isAdmin()
)
Si ces deux conditions sont remplies, nous retournons
load()
, ce qui déclenche le préchargement du composant.Sinon, nous retournons
of(null)
, ce qui signifie que nous ne préchargeons pas le composant.Dans
app.config.ts
, nous utilisons notre stratégie personnalisée avecwithPreloading(AdminPreloadingStrategy)
.
CONSEIL
Cette approche est particulièrement utile lorsque vous avez des sections de votre application qui ne sont pertinentes que pour certains utilisateurs. En préchargeant de manière sélective, vous optimisez l'utilisation des ressources tout en améliorant l'expérience des utilisateurs concernés.
Cette stratégie personnalisée offre plusieurs avantages :
Performance optimisée : Seuls les utilisateurs administrateurs téléchargent le module admin, économisant de la bande passante pour les autres utilisateurs.
Expérience utilisateur améliorée : Les administrateurs bénéficient d'un accès plus rapide à leurs outils, car le module est préchargé.
Sécurité renforcée : Bien que ce ne soit pas une mesure de sécurité en soi, cela ajoute une couche supplémentaire en ne préchargeant pas inutilement du contenu sensible pour les utilisateurs non autorisés.
En créant des stratégies de préchargement personnalisées, vous pouvez adapter finement le comportement de chargement de votre application Angular à vos besoins spécifiques, offrant ainsi une expérience utilisateur optimale tout en gérant efficacement les ressources.
Indiquer si une route doit être préchargée ou non
Dans une application e-commerce, certaines catégories de produits sont souvent plus populaires que d'autres. Nous pouvons utiliser une stratégie de préchargement sélective pour améliorer l'expérience utilisateur en préchargeant ces catégories populaires, tout en laissant les autres se charger à la demande.
Voici comment nous pouvons implémenter cette approche :
typescript
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'electronics',
loadComponent: () => import('./components/features/electronics/electronics.component').then(m => m.ElectronicsComponent),
data: { preload: true } // Catégorie populaire, à précharger
},
{
path: 'clothing',
loadComponent: () => import('./components/features/clothing/clothing.component').then(m => m.ClothingComponent),
data: { preload: true } // Autre catégorie populaire
},
{
path: 'books',
loadComponent: () => import('./components/features/books/books.component').then(m => m.BooksComponent),
data: { preload: false } // Catégorie moins populaire, pas de préchargement
},
{
path: 'furniture',
loadComponent: () => import('./components/features/furniture/furniture.component').then(m => m.FurnitureComponent)
// Pas de data.preload défini, donc pas de préchargement par défaut
}
];
typescript
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
if (route.data && route.data['preload']) {
console.log('Préchargement de la catégorie:', route.path);
return load();
} else {
return of(null);
}
}
}
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
import { routes } from './app.routes';
import { SelectivePreloadingStrategy } from './selective-preloading-strategy';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withPreloading(SelectivePreloadingStrategy)),
provideHttpClient()
]
};
Examinons ce que fait cette approche dans le contexte d'une application e-commerce :
Dans
app.routes.ts
, nous définissons nos routes pour différentes catégories de produits :- Les catégories "electronics" et "clothing" sont marquées avec
data: { preload: true }
, indiquant qu'elles doivent être préchargées. - La catégorie "books" est explicitement marquée pour ne pas être préchargée avec
data: { preload: false }
. - La catégorie "furniture" n'a pas de propriété
preload
, donc elle ne sera pas préchargée par défaut.
- Les catégories "electronics" et "clothing" sont marquées avec
Dans
selective-preloading-strategy.ts
, notre stratégie vérifie la présence dedata.preload
sur chaque route :- Si
route.data['preload']
esttrue
, le composant est préchargé. - Sinon, le composant n'est pas préchargé.
- Si
Dans
app.config.ts
, nous appliquons notreSelectivePreloadingStrategy
à l'ensemble de l'application.
CONSEIL
Cette approche est particulièrement utile pour les sites e-commerce où certaines catégories de produits sont plus fréquemment visitées que d'autres. En préchargeant ces catégories populaires, vous améliorez l'expérience utilisateur pour la majorité de vos visiteurs, tout en optimisant l'utilisation des ressources.
Utiliser l'API Network Information pour optimiser le préchargement
Le préchargement des modules dans Angular peut considérablement améliorer les performances de navigation. Cependant, sur des connexions lentes ou limitées, cela peut avoir l'effet inverse en consommant inutilement de la bande passante.
Le saviez-vous ?
Le préchargement peut affecter négativement les performances sur les réseaux lents en entrant en concurrence avec d'autres requêtes importantes comme les appels AJAX.
L'API Network Information à la rescousse
L'API Network Information nous permet d'obtenir des informations sur la connexion de l'utilisateur en temps réel. Nous allons l'utiliser pour créer une stratégie de préchargement "consciente du réseau".
Voici comment implémenter cette stratégie :
typescript
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, EMPTY } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class NetworkAwarePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, loadFunction: () => Observable<any>): Observable<any> {
if (this.isPreloadingAllowed(route)) {
return loadFunction();
} else {
return EMPTY;
}
}
private isPreloadingAllowed(route: Route): boolean {
const networkInfo = (navigator as any).connection;
if (networkInfo) {
// Mode économie de données
if (networkInfo.saveData) {
console.log('Préchargement désactivé : mode économie de données actif');
return false;
}
// Connexion lente (2G ou moins)
const connectionSpeed = networkInfo.effectiveType || '';
if (connectionSpeed.includes('2g')) {
console.log('Préchargement désactivé : connexion 2G détectée');
return false;
}
// Vérification du temps de latence (RTT)
const latency = networkInfo.rtt;
if (latency && latency > 500) { // 500 ms
console.log('Préchargement désactivé : latence élevée détectée');
return false;
}
// Vérification de la bande passante
const bandwidth = networkInfo.downlink;
if (bandwidth && bandwidth < 1.5) { // Moins de 1.5 Mbps
console.log('Préchargement désactivé : bande passante faible');
return false;
}
}
console.log('Préchargement autorisé : conditions réseau favorables');
return true;
}
}
typescript
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading } from '@angular/router';
import { routes } from './app.routes';
import { NetworkAwarePreloadingStrategy } from './core/preload/network';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes, withPreloading(NetworkAwarePreloadingStrategy)),
]
};
typescript
import { Routes } from '@angular/router';
import { UsersComponent } from './pages/users/users.component';
// on imagine que notre application contient 2 pages :
// - la page d'accueil
// - la page de login
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./pages/users/users.component').then(m => m.UsersComponent)
},
{
path: 'login',
loadChildren: () => import('./pages/login/login.routes')
.then(m => m.routes),
}
];
L'objet networkInfo
(accessible via navigator.connection
) fournit plusieurs propriétés utiles :
saveData
: Indique si le mode économie de données est activé.effectiveType
: Type de connexion (4g, 3g, 2g, slow-2g).rtt
: Round Trip Time en millisecondes.downlink
: Bande passante estimée en Mbps.
En savoir plus
navigator.connection est une propriété de l'objet navigator
en JavaScript. Cette propriété est spécifique au navigateur et n'est pas standardisée, ce qui signifie qu'elle n'est pas supportée par tous les navigateurs.
Tester dans le navigateur avec limitation de bande passante
Attention
Cette méthode ne fonctionne que dans les navigateurs qui supportent l'API Network Information. Essayez d'utiliser un navigateur moderne comme Chrome.
Pour tester votre stratégie de préchargement avec différentes conditions réseau :
- Ouvrez les DevTools de votre navigateur (F12 ou Ctrl+Shift+I).
- Allez dans l'onglet "Network".
- Cherchez l'option "Throttling" (généralement en haut de l'onglet).
- Choisissez un profil prédéfini (comme "3G") ou créez un profil personnalisé.
Pour créer un profil personnalisé :
- Cliquez sur "Add" ou "Custom" dans le menu déroulant Throttling.
- Définissez la vitesse de téléchargement, la vitesse d'envoi et la latence.
Exemple de profils :
- Fast 3G: 1.5 Mbps download, 750 Kbps upload, 40ms RTT
- Slow 3G: 780 Kbps download, 330 Kbps upload, 200ms RTT
Une fois le throttling activé, rechargez votre application et observez comment votre stratégie de préchargement réagit aux différentes conditions réseau simulées. Si vous avez une connexion lente, vous verrez que les composants ne sont pas préchargés. A contrario, si vous avez une connexion rapide, les composants seront préchargés.
CONSEIL
N'oubliez pas de désactiver le throttling après vos tests pour revenir à des conditions normales !
Cas d'usage
Si vous ne voyez pas comment appliquer une stratégie de préchargement à votre application, voici quelques exemples de cas d'usage et les stratégies suggérées :
Cas d'usage | Description | Stratégie suggérée |
---|---|---|
Application à charge légère | Petite application avec peu de modules | PreloadAllModules |
Application à charge lourde | Grande application avec de nombreux modules | Stratégie personnalisée basée sur la priorité |
Application e-commerce | Préchargement des catégories populaires | Stratégie sélective basée sur les données de route |
Application mobile | Optimisation pour les connexions variables | NetworkAwarePreloadingStrategy |
Application avec authentification | Préchargement différent pour utilisateurs connectés/non connectés | Stratégie personnalisée basée sur l'état d'authentification |
Application avec navigation complexe | Préchargement basé sur la structure de navigation | Stratégie basée sur la profondeur des routes |
Application avec utilisation saisonnière | Préchargement adapté aux périodes de forte affluence | Stratégie basée sur le temps/date |
Application avec analyse utilisateur | Préchargement basé sur le comportement de l'utilisateur | Stratégie d'apprentissage automatique |
Application à forte interaction visuelle | Préchargement des modules visibles à l'écran | Stratégie basée sur le viewport |
Application avec contenu géolocalisé | Préchargement basé sur la localisation de l'utilisateur | Stratégie personnalisée utilisant la géolocalisation |
Application avec mode hors ligne | Préchargement adapté à la disponibilité du réseau | Stratégie combinant NetworkAwarePreloadingStrategy et stockage local |
Application multilingue | Préchargement basé sur la langue de l'utilisateur | Stratégie personnalisée basée sur les préférences linguistiques |
Application avec thèmes | Préchargement des ressources spécifiques au thème | Stratégie basée sur les préférences de thème de l'utilisateur |
Application avec fonctionnalités premium | Préchargement différencié pour utilisateurs gratuits/premium | Stratégie basée sur le type d'abonnement |
Application avec mises à jour fréquentes | Préchargement des nouveaux modules | Stratégie basée sur la version des modules |
Les paquets de préchargement
Il existe des paquets de préchargement pour Angular qui peuvent vous aider à optimiser les performances de votre application.
Attention
C'est une liste de paquets non-officiels. Assurez-vous de vérifier leur compatibilité avec votre version d'Angular et avec les navigateurs que vous utilisez.
ngx-quicklink
Il fournit une stratégie de préchargement du routeur qui télécharge automatiquement les modules paresseux associés à tous les liens visibles à l'écran.
https://github.com/mgechev/ngx-quicklink
ngx-hover-preload
Ce paquet exporte une stratégie de préchargement (PreloadingStrategy), qui préchargera un itinéraire chargé paresseusement au passage de la souris sur le lien du routeur correspondant.