Appearance
@defer : Le chargement différé des vues
La directive @defer
est une des nouveautés majeures d'Angular 17. Elle révolutionne la façon dont nous gérons le chargement paresseux (lazy loading) de nos composants. Voyons ensemble comment l'utiliser efficacement pour optimiser vos applications.
Comprendre le Concept
Imaginez un restaurant qui prépare tous ses plats à l'avance, avant même l'arrivée des clients. Ce serait inefficace et potentiellement du gaspillage ! De la même manière, charger tous les composants d'une application dès le démarrage n'est pas optimal.
Avec @defer
, Angular nous permet de "préparer les plats à la demande", c'est-à-dire de charger les composants uniquement quand ils sont nécessaires.
AVANTAGE CLÉ
@defer
permet de réduire significativement le temps de chargement initial de votre application en ne chargeant que ce qui est strictement nécessaire au départ.
ATTENTION
@defer
ne peut être utilisé que sur les composants standalone. De plus, les dépendances ne peuvent pas être référencées en dehors des blocs @defer
dans le même fichier. Si elles sont référencées en dehors du bloc @defer
ou référencées dans des requêtes ViewChild
, les dépendances seront chargées immédiatement.
Cas d'Usage
Cas d'Usage | Description | Exemple de Fonctionnalité |
---|---|---|
Composants Modaux | Charger les composants de dialogue uniquement lors du clic | Modal de confirmation de suppression d'un utilisateur |
Widgets Complexes | Différer le chargement des composants avec beaucoup de logique ou de données | Tableau de bord avec graphiques et statistiques utilisateur |
Formulaires Dynamiques | Charger les formulaires complexes à la demande | Formulaire d'édition de profil utilisateur avec validation avancée |
Composants Tiers | Retarder le chargement des bibliothèques externes volumineuses | Éditeur de texte riche ou carte interactive |
Sections Conditionnelles | Charger du contenu basé sur le rôle utilisateur | Interface d'administration accessible uniquement aux administrateurs |
Syntaxe de Base
Voici la structure de base d'un bloc @defer
:
ts
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { UserListComponent } from './user-list/user-list.component';
import { LoadingSpinnerComponent } from './loading-spinner/loading-spinner.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [CommonModule, UserListComponent, LoadingSpinnerComponent],
template: `
@defer {
<app-user-list [users]="users" />
} @placeholder {
<div>Chargement de la liste...</div>
} @loading {
<app-loading-spinner />
} @error {
<div>Erreur de chargement</div>
}
`
})
export class AppComponent {
users: User[] = [];
}
Explication des Blocs
@defer
- C'est le bloc principal qui contient le contenu à charger de manière différée
- Dans notre exemple, il contient le composant
UserListComponent
qui sera chargé uniquement lorsque nécessaire - Sans condition de déclenchement spécifiée, le chargement commence dès que possible
@placeholder (minimum)
- Affiché immédiatement à la place du contenu différé
- Reste visible jusqu'à ce que le contenu différé soit prêt
- Accepte un paramètre
minimum
pour définir la durée minimale d'affichage - Idéal pour afficher une version simplifiée ou un "squelette" de votre contenu
ts
@defer {
<app-user-list [users]="users" />
} @placeholder (minimum 500ms) {
<div>Chargement de la liste...</div>
}
- @loading (after, minimum)
- S'affiche pendant le chargement actif du contenu
- Accepte deux paramètres optionnels :
after
: délai avant l'affichage du loading (évite le clignotement)minimum
: durée minimale d'affichage du loading
- Particulièrement utile pour les chargements qui prennent du temps
ts
@defer {
<app-user-list [users]="users" />
} @loading (after 100ms; minimum 1s) {
<app-loading-spinner />
}
ANTI-CLIGNOTEMENT
Les paramètres after
et minimum
du bloc @loading
sont essentiels pour éviter le "flickering" (clignotement) de l'interface lorsque le chargement est très rapide. Par exemple :
after 100ms
: le loading ne s'affichera que si le chargement dure plus de 100msminimum 1s
: une fois affiché, le loading restera visible au moins 1 seconde
- @error
- S'affiche si le chargement du contenu échoue
- Permet de gérer gracieusement les erreurs
- Idéal pour afficher un message d'erreur explicite à l'utilisateur
ORDRE D'AFFICHAGE
- Le
@placeholder
s'affiche en premier - Lors du déclenchement du chargement, le
@loading
remplace le placeholder - Une fois le chargement terminé, le contenu du bloc
@defer
s'affiche - En cas d'erreur, le bloc
@error
s'affiche
Déclencheurs Déclaratifs "on"
1. immediate
Le déclencheur immediate
est particulièrement intéressant car il permet de charger le composant dès que possible tout en bénéficiant des avantages du chargement différé.
En effet, même si le composant est chargé immédiatement, il est importé dynamiquement dans un chunk séparé. Cela signifie que le bundle principal de l'application reste léger. Par exemple, si votre application a une page de connexion qui n'utilise pas le profil utilisateur, le code du composant de profil ne sera pas chargé inutilement.
ts
import { Component } from '@angular/core';
import { UserDetailsComponent } from './user-details.component';
@Component({
selector: 'app-user-profile',
standalone: true,
template: `
@defer (on immediate) {
<app-user-details [user]="user" />
} @placeholder {
<div>Chargement du profil...</div>
}
`,
imports: [UserDetailsComponent],
})
export class UserProfileComponent {
// ...
}
2. idle (par défaut)
Ce déclencheur attend que le navigateur soit en état d'inactivité pour charger le composant.
ts
@defer (on idle) {
<app-analytics-dashboard />
} @placeholder {
<div>Le tableau de bord se charge...</div>
}
CONSEIL
Parfait pour les composants non critiques qui peuvent être chargés en arrière-plan sans impacter l'expérience utilisateur.
3. timer(delay)
Permet de retarder le chargement d'un composant d'une durée spécifique.
ts
@defer (on timer(2000)) { // Délai de 2 secondes
<app-notification-panel />
} @placeholder {
<div>Les notifications arrivent...</div>
}
ATTENTION
Utilisez ce déclencheur avec parcimonie. Un délai trop long peut frustrer les utilisateurs.
4. viewport
Déclenche le chargement lorsque l'élément entre dans la zone visible de l'écran.
ts
import { Component } from '@angular/core';
import { UserCardComponent } from './user-card.component';
import { UserCardSkeletonComponent } from './user-card-skeleton.component';
@Component({
selector: 'app-user-list',
standalone: true,
imports: [UserCardComponent, UserCardSkeletonComponent],
template: `
<div class="user-list">
@for (user of users; track user.id) {
@defer (on viewport) {
<app-user-card [user]="user" />
} @placeholder {
<app-user-card-skeleton />
}
}
</div>
`
})
export class UserListComponent {
// ...
}
OPTIMISATION
Particulièrement utile pour les listes longues ou les images qui ne doivent être chargées que lorsqu'elles sont visibles.
5. hover
Déclenche le chargement lorsque l'utilisateur survole un élément.
ts
@defer (on hover) {
<app-user-details [userId]="user.id" />
} @placeholder {
<div>Survolez pour voir les détails</div>
}
ASTUCE
Idéal pour les info-bulles complexes ou les menus détaillés.
6. interaction
Charge le composant lors de la première interaction utilisateur (clic, toucher, etc.).
ts
@defer (on interaction) {
<app-comments-section [postId]="post.id" />
} @placeholder {
<button>Afficher les commentaires</button>
}
Combinaison de Déclencheurs
Vous pouvez combiner plusieurs déclencheurs pour plus de flexibilité :
ts
@defer (on viewport; on hover) {
<app-complex-widget />
} @placeholder {
<div>Widget en attente</div>
}
Préchargement Intelligent
Le préchargement intelligent (prefetch
) permet d'optimiser l'expérience utilisateur en chargeant le composant en arrière-plan avant qu'il ne soit réellement nécessaire. Par exemple, si vous avez un composant qui se charge au scroll, vous pouvez commencer à le précharger pendant les moments d'inactivité du navigateur :
EXEMPLE CONCRET
Imaginez un catalogue de produits. Quand l'utilisateur fait défiler la page, les produits se chargent au fur et à mesure qu'ils entrent dans le viewport. Avec le préchargement, pendant que l'utilisateur regarde les premiers produits, Angular charge déjà en arrière-plan les produits suivants. Ainsi, quand l'utilisateur continue à scroller, les produits apparaissent instantanément car ils sont déjà chargés !
ts
@defer (on viewport; prefetch on idle) {
<app-user-statistics [userId]="user.id" />
} @placeholder {
<div>Statistiques en cours de chargement...</div>
}
Déclencheurs Impératifs "when"
Les déclencheurs impératifs "when" offrent une flexibilité maximale en vous permettant de définir vos propres conditions de chargement. Contrairement aux déclencheurs "on" qui sont prédéfinis, les déclencheurs "when" vous permettent d'écrire une logique personnalisée.
Cas d'Utilisation Courants
1. Conditions Basées sur l'État
ts
import { Component, signal } from '@angular/core';
import { ProfileDetailsComponent } from './profile-details.component';
import { ProfileSetupComponent } from './profile-setup.component';
@Component({
template: `
@defer (when userProfile.isComplete) {
<app-profile-details [user]="userProfile" />
} @placeholder {
<app-profile-setup />
}
`,
standalone: true,
imports: [ProfileDetailsComponent, ProfileSetupComponent],
})
export class UserProfileComponent {
userProfile = signal<UserProfile>({ isComplete: false });
}
2. Conditions Multiples
ts
@defer (when isAuthenticated() && hasPermission('VIEW_STATS')) {
<app-analytics-dashboard />
} @placeholder {
<app-unauthorized-message />
}
3. Conditions Asynchrones
ts
import { Component, inject } from '@angular/core';
// on part du principe que nous avons un service FeatureService qui nous permet de vérifier si une fonctionnalité est activée
import { FeatureService } from './feature.service';
import { AsyncPipe } from '@angular/common';
import { FeatureUnavailableComponent } from './feature-unavailable.component';
import { NewFeatureComponent } from './new-feature.component';
@Component({
template: `
@defer (when featureFlag$ | async) {
<app-new-feature />
} @placeholder {
<app-feature-unavailable />
}
`,
standalone: true,
imports: [FeatureUnavailableComponent, NewFeatureComponent, AsyncPipe],
})
export class FeatureComponent {
featureFlag$ = this.featureService.isEnabled('NEW_FEATURE');
}
Patterns Avancés
1. Combinaison avec des Observables
ts
import { Component, inject } from '@angular/core';
// on part du principe que nous avons un service DataService qui nous permet de récupérer les données
import { DataService } from './data.service';
import { DataVisualizationComponent } from './data-visualization.component';
import { LoadingSpinnerComponent } from './loading-spinner.component';
import { ErrorMessageComponent } from './error-message.component';
import { AsyncPipe } from '@angular/common';
@Component({
template: `
@defer (when data$ | async; when !isLoading()) {
<app-data-visualization [data]="data$ | async" />
} @loading {
<app-loading-spinner />
} @error {
<app-error-message />
}
`,
standalone: true,
imports: [DataVisualizationComponent, LoadingSpinnerComponent, ErrorMessageComponent, AsyncPipe],
})
export class DataViewComponent {
data$ = this.dataService.getData();
isLoading = signal(false);
}
2. Gestion des Étapes
ts
import { Component, signal } from '@angular/core';
import { ProfileStepComponent } from './profile-step.component';
import { PreferencesStepComponent } from './preferences-step.component';
import { ConfirmationStepComponent } from './confirmation-step.component';
@Component({
template: `
@defer (when currentStep() === 'PROFILE') {
<app-profile-step />
}
@defer (when currentStep() === 'PREFERENCES') {
<app-preferences-step />
}
@defer (when currentStep() === 'CONFIRMATION') {
<app-confirmation-step />
}
`,
standalone: true,
imports: [ProfileStepComponent, PreferencesStepComponent, ConfirmationStepComponent],
})
export class WizardComponent {
currentStep = signal<'PROFILE' | 'PREFERENCES' | 'CONFIRMATION'>('PROFILE');
}
Bonnes Pratiques
PERFORMANCE
- Évitez les Calculs Complexes
- Gardez vos conditions simples et efficaces
- Évitez les opérations coûteuses dans les conditions
ts
// ❌ À éviter
@defer (when users.filter(u => u.isActive).length > 0) {
<app-user-list />
}
// ✅ Préférez
@defer (when hasActiveUsers()) {
<app-user-list />
}
LISIBILITÉ
- Extrayez les Conditions Complexes
- Créez des méthodes dédiées pour les conditions complexes
- Améliorez la maintenabilité du code
ts
// ❌ À éviter
@defer (when user && user.roles.includes('ADMIN') && !isLoading) {
<app-admin-panel />
}
// ✅ Préférez
@defer (when canAccessAdminPanel()) {
<app-admin-panel />
}
ATTENTION
- Gestion des États
- Gérez correctement les états initiaux
- Prévoyez les cas d'erreur
ts
@defer (when userState() !== 'LOADING') {
<app-user-content />
} @loading {
<app-loading-spinner />
} @error {
<app-error-state />
}
Hydratation Incrémentielle (Angular 19+)
L'hydratation incrémentielle est une nouvelle fonctionnalité introduite dans Angular 19 qui améliore significativement les performances du rendu côté serveur (SSR).
Pour activer l'hydratation incrémentielle, vous devez d'abord configurer votre application :
ts
import { provideClientHydration, withIncrementalHydration } from '@angular/platform-browser';
bootstrapApplication(AppComponent, {
providers: [
provideClientHydration(withIncrementalHydration())
]
});
L'hydratation incrémentielle s'intègre parfaitement avec la directive @defer
en utilisant le déclencheur hydrate
:
ts
import { Component } from '@angular/core';
import { UserStatisticsComponent } from './user-statistics.component';
@Component({
template: `
<header>Dashboard Utilisateur</header>
@defer (hydrate on viewport) {
<app-user-statistics [userId]="user.id" />
} @placeholder {
<div>Chargement des statistiques...</div>
}
`,
standalone: true,
imports: [UserStatisticsComponent],
})
export class UserDashboardComponent {
// ...
}
Il faut activer le SSR pour que l'hydratation incrémentielle fonctionne. Voir l'article Server-side rendering pour plus d'informations.