Angular @defer en 2026 : lazy loading declaratif complet

Plongee technique dans les blocs Angular @defer pour le lazy loading declaratif. Declencheurs, prefetching, hydratation incrementale, comportement SSR et patterns de performance concrets pour preparer les entretiens techniques Angular.

Angular @defer lazy loading

Les applications Angular modernes embarquent des centaines de composants, et la taille du bundle JavaScript initial en subit les consequences. Pendant des annees, le lazy loading par le routeur constituait la seule reponse native pour differer le chargement de code. Cette approche fonctionne au niveau des routes, mais elle ne resout pas le probleme des composants lourds affiches au sein d'une meme page : un graphique interactif en bas de page, un panneau d'export rarement sollicite, ou un widget de chat affiche apres quelques secondes d'inactivite.

Les blocs @defer, introduits dans Angular 17 et stabilises a travers Angular 22, apportent une reponse a cette granularite manquante. Un simple bloc declaratif dans le template suffit pour extraire un composant standalone dans un chunk JavaScript separe, charge uniquement lorsqu'une condition de declenchement est remplie. Plus besoin de manipuler import() manuellement, de configurer des strategies de preloading complexes, ou de jongler avec *ngIf et des flags booleens pour simuler un chargement conditionnel.

Le resultat concret : le navigateur telecharge moins de JavaScript au premier chargement, ce qui ameliore le Largest Contentful Paint (LCP) et le Time to Interactive (TTI) sans restructurer l'architecture de l'application.

Ce que fait @defer en une phrase

@defer ordonne au compilateur Angular d'extraire les composants standalone enveloppes dans des chunks JavaScript separes, charges uniquement lorsqu'un declencheur se declenche (viewport, interaction, timer, condition). Le navigateur telecharge moins de code au demarrage, et les metriques Core Web Vitals s'ameliorent sans configuration manuelle de code-splitting.

Comment fonctionne @defer en interne

Lorsque le compilateur Angular rencontre un bloc @defer, il analyse les dependances du template -- composants, directives, pipes -- et les extrait dans un chunk separe. Le bundle principal est livre sans ces dependances. Au moment de l'execution, Angular evalue la condition de declenchement et charge le chunk via un appel dynamique import().

Le bloc prend en charge quatre sous-blocs qui controlent ce que l'utilisateur voit pendant le cycle de vie du chargement :

app.component.htmltypescript
@defer (on viewport) {
  <analytics-dashboard />
} @placeholder {
  <div class="h-64 bg-muted animate-pulse"></div>
} @loading (minimum 200ms) {
  <loading-spinner />
} @error {
  <p>Failed to load the dashboard. Please refresh.</p>
}

@placeholder s'affiche avant que le declencheur ne soit active. C'est le contenu visible par defaut, y compris lors du rendu cote serveur. @loading apparait pendant le telechargement du chunk, avec un parametre minimum optionnel pour eviter un flash de spinner lorsque le chunk se charge rapidement depuis le cache. @error capture les echecs reseau ou les erreurs de chargement du chunk.

Une contrainte essentielle : chaque dependance a l'interieur d'un bloc @defer doit etre standalone. Les composants declares dans un NgModule ne peuvent pas etre differes et seront charges de maniere eager, sans qu'aucune erreur ne soit levee. L'optimisation echoue silencieusement, ce qui rend les composants standalone indispensables pour toute strategie de lazy loading a la maille composant.

Les types de declencheurs

Angular fournit sept declencheurs integres, chacun ciblant une strategie de chargement differente. Plusieurs declencheurs peuvent etre combines avec des points-virgules et sont evalues comme des conditions OR.

product-page.component.htmltypescript
// Loads when the browser goes idle (default)
@defer {
  <recommendation-engine />
}

// Loads when the element enters the viewport
@defer (on viewport) {
  <customer-reviews [productId]="product.id" />
}

// Loads on click or keydown
@defer (on interaction) {
  <product-configurator />
} @placeholder {
  <button>Configure this product</button>
}

// Loads on mouseenter or focusin
@defer (on hover) {
  <quick-preview />
}

// Loads after 3 seconds
@defer (on timer(3s)) {
  <chat-widget />
}

// Loads immediately after initial render
@defer (on immediate) {
  <notification-center />
}

Le declencheur on viewport utilise en interne l'Intersection Observer API. Il observe l'element du placeholder et declenche le chargement des que cet element entre dans la zone visible du navigateur. Le declencheur on idle delegue a requestIdleCallback, ce qui en fait le choix le plus sur par defaut pour le contenu situe sous la ligne de flottaison, car le chargement n'intervient qu'une fois que le navigateur a termine ses taches prioritaires. Le declencheur on timer accepte des durees en millisecondes (500ms) ou en secondes (3s), ce qui le rend adapte aux widgets de chat ou aux notifications differees.

Le declencheur when pour les conditions reactives

Au-dela des declencheurs bases sur des evenements, when accepte toute expression booleenne, y compris la lecture de signals Angular :

dashboard.component.tstypescript
export class DashboardComponent {
  showAdvanced = signal(false);
}

// dashboard.component.html
@defer (when showAdvanced()) {
  <advanced-analytics />
} @placeholder {
  <button (click)="showAdvanced.set(true)">Show advanced analytics</button>
}

Combiner on et when est valide. Angular les traite comme un OR logique : le bloc se charge des que l'une des deux conditions est remplie. Cette approche permet de couvrir simultanement un declencheur passif (viewport, idle) et une condition reactive basee sur l'etat applicatif.

Le prefetching : separer le telechargement de l'affichage

Le prefetching decouple le moment ou le chunk est telecharge du moment ou le composant est effectivement rendu. Cette separation elimine la latence percue en recuperant le JavaScript avant que l'utilisateur ne declenche le chargement.

settings.component.htmltypescript
@defer (on interaction; prefetch on idle) {
  <account-settings />
} @placeholder {
  <button>Open settings</button>
}

Dans ce pattern, le navigateur telecharge le chunk account-settings pendant les periodes d'inactivite. Lorsque l'utilisateur clique, le composant s'affiche instantanement car le code est deja disponible en memoire. Le prefetching accepte les memes types de declencheurs que la clause principale on.

Un pattern recurrent pour les tableaux de bord : prefetcher les composants lourds pendant les periodes d'inactivite tout en differant l'affichage jusqu'a l'entree dans le viewport.

analytics.component.htmltypescript
@defer (on viewport; prefetch on idle) {
  <chart-widget [data]="salesData()" />
} @placeholder (minimum 100ms) {
  <div class="h-48 bg-muted rounded-none"></div>
}

Le parametre minimum sur @placeholder empeche un saut de mise en page lorsque l'utilisateur scrolle rapidement et que le composant differe se charge presque immediatement. Ce detail est souvent neglige, mais il evite une degradation visuelle perceptible par l'utilisateur.

Prêt à réussir tes entretiens Angular ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

@defer et le rendu cote serveur (SSR)

Cote serveur, les blocs @defer affichent par defaut le contenu de @placeholder. Le composant differe n'est jamais rendu cote serveur. Ce comportement est intentionnel : le telechargement du chunk n'a de sens que dans un contexte navigateur, ou import() et les API du DOM sont disponibles.

Pour le contenu critique en termes de SEO, cela implique que le @placeholder doit contenir du HTML semantiquement significatif, et non un simple spinner :

blog-post.component.htmltypescript
@defer (on viewport) {
  <comment-section [postId]="post.id" />
} @placeholder {
  <section aria-label="Comments">
    <h2>Comments</h2>
    <p>Loading comments...</p>
  </section>
}

Les moteurs de recherche indexent le contenu du placeholder pendant le SSR. Un placeholder descriptif garantit que la page reste semantiquement complete et conserve sa valeur SEO, meme avant le chargement du composant differe cote client.

L'hydratation incrementale avec @defer

Angular 19 a introduit l'hydratation incrementale en developer preview, et Angular 22 la stabilise comme fonctionnalite prete pour la production. L'hydratation incrementale etend @defer avec un declencheur hydrate qui controle le moment ou un composant rendu cote serveur devient interactif cote client.

product-page.component.htmltypescript
@defer (hydrate on viewport) {
  <product-gallery [images]="product.images" />
}

@defer (hydrate on interaction) {
  <product-reviews [productId]="product.id" />
}

Contrairement au @defer standard, l'hydratation incrementale effectue le rendu complet du HTML du composant cote serveur. Le client recoit un balisage complet mais reporte l'hydratation du composant jusqu'au declenchement du trigger. Le resultat : le navigateur peint la page complete immediatement, mais l'execution du JavaScript est differee jusqu'a ce que l'utilisateur interagisse ou que l'element entre dans le viewport.

L'equipe Angular rapporte des ameliorations constantes de 40 a 50 % des scores LCP lors de l'utilisation de l'hydratation incrementale sur des pages riches en contenu. Angular DevTools dans la version 22 visualise les etats d'hydratation (pending, hydrated, error) directement dans l'inspecteur de composants, ce qui facilite le debug et le suivi de la couverture d'hydratation.

Patterns concrets et strategie de performance

Pattern 1 : tableau de bord avec plusieurs panneaux differes

dashboard.component.htmltypescript
<div class="grid grid-cols-2 gap-4">
  @defer (on viewport; prefetch on idle) {
    <sales-chart />
  } @placeholder {
    <skeleton-card height="300px" />
  }

  @defer (on viewport; prefetch on idle) {
    <user-activity-feed />
  } @placeholder {
    <skeleton-card height="300px" />
  }

  @defer (on interaction) {
    <export-panel />
  } @placeholder {
    <button>Export data</button>
  }
</div>

Chaque panneau se charge independamment. Le panneau d'export reste non charge tant que l'utilisateur ne l'a pas explicitement demande, ce qui economise le telechargement d'un chunk complet pour les utilisateurs qui n'exportent jamais de donnees. Les panneaux de graphiques et d'activite utilisent une combinaison viewport/idle qui garantit un chargement fluide sans bloquer le rendu initial.

Pattern 2 : chargement conditionnel de fonctionnalites avec les signals

editor.component.tstypescript
export class EditorComponent {
  user = inject(UserService).currentUser;
  isPremium = computed(() => this.user()?.plan === 'premium');
}

// editor.component.html
@defer (when isPremium()) {
  <ai-assistant />
} @placeholder {
  <upgrade-banner />
}

Les utilisateurs en offre gratuite ne telechargent jamais le chunk de l'assistant IA. La banniere de mise a niveau sert a la fois de placeholder et de levier de conversion. Ce pattern illustre comment @defer peut combiner optimisation de performance et logique metier, en ne chargeant du code que pour les utilisateurs qui y ont acces.

Eviter les pieges courants

Imbriquer des blocs @defer avec le meme declencheur provoque des telechargements simultanes de chunks, annulant le benefice de la repartition progressive des chargements. Il faut echelonner les declencheurs entre les blocs imbriques :

typescript
// Avoid: both fire on idle simultaneously
@defer {
  @defer {
    <nested-heavy-component />
  }
}

// Better: outer on idle, inner on viewport
@defer (on idle) {
  <wrapper-component />
}
// Inside wrapper-component template:
@defer (on viewport) {
  <nested-heavy-component />
}

Autre piege frequent : utiliser @defer pour des composants deja dans le chemin de rendu critique. Differer du contenu au-dessus de la ligne de flottaison augmente le LCP car le navigateur doit telecharger et executer le chunk avant de peindre le contenu visible. Il convient de reserver @defer au contenu sous la ligne de flottaison ou au contenu declenche par une action utilisateur explicite.

@defer vs lazy loading par le routeur

Le lazy loading par le routeur et @defer sont complementaires, pas concurrents. Le lazy loading par le routeur decoupe au niveau des routes, en differant des modules fonctionnels entiers. @defer decoupe au sein d'un template, en differant des composants individuels.

| Aspect | Router Lazy Loading | @defer | |--------|-------------------|--------| | Granularity | Route level | Component level | | Trigger | Navigation event | viewport, idle, interaction, hover, timer, condition | | SSR behavior | Full server render | Placeholder on server | | Requires standalone | No (works with NgModules) | Yes (standalone only) | | Prefetching | preloadingStrategy | prefetch on clause | | Use case | Feature modules, pages | Widgets, panels, conditional UI |

Une application typique utilise les deux approches simultanement : le lazy loading par le routeur pour les grandes zones fonctionnelles (pages, modules de feature) et @defer pour les composants lourds a l'interieur de ces zones (graphiques, panneaux d'export, widgets conditionnels).

Questions d'entretien sur Angular @defer

Les entretiens techniques couvrent de plus en plus @defer a mesure qu'il devient central dans la strategie de performance Angular. Voici les questions qui reviennent regulierement dans les questions d'entretien Angular.

Q : Que se passe-t-il si un composant non-standalone est place dans un bloc @defer ? Le composant se charge de maniere eager, independamment du bloc @defer. Le compilateur Angular ne peut pas extraire les composants declares dans un NgModule dans des chunks separes. Aucune erreur n'est levee, mais l'optimisation echoue silencieusement. C'est l'un des pieges les plus frequents en entretien, car la syntaxe du template ne signale pas le probleme.

Q : Quel est le comportement de @defer pendant le SSR ? Le serveur rend le contenu de @placeholder. Le composant differe n'est jamais rendu cote serveur. L'hydratation se produit cote client lorsque le declencheur se declenche. Avec l'hydratation incrementale (hydrate on), le HTML complet est rendu cote serveur mais le JavaScript n'est execute qu'au declenchement du trigger cote client.

Q : @defer est-il compatible avec la strategie de detection de changement OnPush ? Oui. @defer est une fonctionnalite au niveau du template et fonctionne avec toute strategie de detection de changement. Le composant differe suit sa propre configuration de detection de changement une fois charge. Il n'y a aucune interaction entre le mecanisme de defer et la strategie OnPush.

Q : Quelle est la difference entre on idle et on immediate ? on idle attend que le navigateur ait termine tout le travail en cours via requestIdleCallback. on immediate se declenche juste apres qu'Angular a termine la passe de rendu initiale, sans attendre l'etat d'inactivite. on immediate est utile pour les composants qui doivent se charger rapidement sans bloquer le premier affichage, comme un centre de notifications.

Pour une preparation plus approfondie aux entretiens Angular, consulter le module de detection de changement Angular et le module Angular signals.

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Conclusion

  • @defer extrait les composants standalone dans des chunks separes charges a la demande, reduisant la taille du bundle initial sans restructurer les routes
  • Sept types de declencheurs (idle, viewport, interaction, hover, timer, immediate, when) couvrent toutes les strategies de chargement, du passif au pilote par l'utilisateur
  • prefetch on decouple le telechargement du chunk de l'affichage du composant, eliminant la latence percue sur les composants declenches par l'utilisateur
  • L'hydratation incrementale (hydrate on) effectue le rendu HTML complet cote serveur tout en differant l'execution JavaScript cote client, offrant des ameliorations de LCP de 40 a 50 % sur les pages riches en contenu
  • Le contenu de @placeholder est rendu pendant le SSR et doit etre semantiquement significatif pour le SEO
  • @defer est complementaire du lazy loading par le routeur : les routes pour le decoupage au niveau fonctionnel, @defer pour le decoupage au niveau des composants dans les templates
  • Les composants non-standalone ne peuvent pas etre differes et se chargent silencieusement de maniere eager

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

#angular
#performance
#lazy-loading

Partager

Articles similaires