AngularAtlaséd. 22.2

Chap. 16UI avancée et performance69 / 75

Listes géantes : virtual scroll

100 000 lignes sans latence : le virtual scroll du CDK, itemSize, trackBy et les alternatives (pagination, scroll infini).

Chaque ligne d’une liste, ce sont des éléments DOM, des styles calculés, du layout et, dans Angular, une vue avec des Bindings vérifiés par la Change Detection. Avec quelques centaines de lignes, vous ne le remarquez pas. Avec des dizaines de milliers, la page met plusieurs secondes à démarrer, le défilement saccade et la mémoire ne cesse de croître. La solution classique : ne pas rendre ce que l’on ne voit pas.

Le Virtual Scroll ne garde dans le DOM que les lignes de la zone visible, plus une marge de réserve au-dessus et en dessous. Au fil du défilement, les mêmes éléments sont réutilisés pour d’autres données. La taille totale de la liste est simulée : la barre de défilement se comporte comme si elle correspondait à toute la liste.

Pourquoi c’est important
Les composants OnPush et les Signals réduisent la fréquence des rendus. Ils ne réduisent pas la quantité rendue. Avec 100 000 lignes, le problème est le nombre de nœuds, pas la Change Detection. Seuls la virtualisation, la pagination ou le chargement par blocs le résolvent.

CDK Virtual Scroll

Depuis @angular/cdk/scrolling, vous utilisez trois éléments Standalone : CdkVirtualScrollViewport (le conteneur de défilement), CdkFixedSizeVirtualScroll (la stratégie : l’attribut itemSize) et CdkVirtualForOf (la directive *cdkVirtualFor, qui remplace @for). Le viewport doit avoir une hauteur, et itemSize doit être exactement la hauteur réelle d’une ligne en pixels. La stratégie s’en sert pour déterminer quels indices sont visibles.

import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';

@Component({
  selector: 'app-orders',
  imports: [CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf],
  template: `
    <cdk-virtual-scroll-viewport itemSize="36" class="viewport">
      <div class="row" *cdkVirtualFor="let o of orders(); trackBy: byId">
        {{ o.id }} · {{ o.customer }} · {{ o.total }}
      </div>
    </cdk-virtual-scroll-viewport>
  `,
  styles: `
    .viewport { height: 480px; }       /* le viewport DOIT avoir une hauteur */
    .row { height: 36px; }             /* = itemSize, exactement */
  `,
})
export class Orders {
  readonly orders = input.required<Order[]>();
  protected readonly byId = (_: number, o: Order) => o.id;
}
@for ne virtualise pas
Le bloc @for rend toujours tous les éléments. Pour virtualiser, il vous faut la directive *cdkVirtualFor, qui reçoit du viewport la plage d’indices à rendre.
Live100 000 lignes (vrai CDK)
Éléments DOM dans la liste : 0rendu initial : 0 mspremière ligne visible : #1
Regardez le compteur « Éléments DOM » : en mode virtuel, il en reste quelques centaines, même en défilant très loin. Sautez à la ligne 50 000 avec scrollToIndex. Passez à un @for simple : seulement 5 000 lignes, mais des dizaines de fois plus de nœuds et un rendu initial nettement plus lent.

Le buffer et le recyclage des vues

minBufferPx et maxBufferPx contrôlent la marge rendue hors de l’écran. Quand la marge descend sous minBufferPx, le viewport rend de nouvelles lignes jusqu’à atteindre maxBufferPx. Un buffer plus grand signifie moins de trous lors d’un défilement rapide, mais plus de DOM. Les vues qui quittent l’écran sont conservées dans un cache (templateCacheSize) et réutilisées. C’est pourquoi trackBy compte : sans lui, bien plus de DOM que nécessaire est recréé à chaque défilement.

<!-- minBufferPx : quand la réserve (au-dessus et en dessous) tombe sous ce nombre de px, il rend davantage… maxBufferPx : …jusqu’à atteindre ce nombre de px -->
<cdk-virtual-scroll-viewport
  itemSize="36"
  minBufferPx="360"
  maxBufferPx="720"
  (scrolledIndexChange)="first.set($event)">
  <div *cdkVirtualFor="let r of rows; trackBy: byId; templateCacheSize: 40">…</div>
</cdk-virtual-scroll-viewport>

Hauteurs variables et autres limites

La stratégie standard suppose des lignes de même hauteur. Une stratégie autosize pour des éléments de tailles différentes existe dans @angular/cdk-experimental, mais la documentation du CDK dit explicitement qu’elle n’est pas prête pour la production. Si vos lignes diffèrent, vous avez trois options :

  • leur donner la même hauteur (texte tronqué, détails au clic) ;
  • écrire votre propre VirtualScrollStrategy ;
  • choisir l’une des alternatives ci-dessous.

Il y a aussi un coût UX : les lignes non rendues n’existent pas dans le DOM. Ctrl+F ne les trouvera pas, et les lecteurs d’écran ignorent leur existence.

Alternatives : le chargement par blocs

Beaucoup de listes n’ont pas besoin de virtualisation : elles ne doivent simplement pas tout charger d’un coup. La pagination est la plus simple et la plus accessible. Le défilement infini ajoute des pages quand l’utilisateur atteint un élément « sentinelle », observé avec IntersectionObserver ou avec @defer (on viewport) (voir @defer). La propriété CSS content-visibility: auto conserve le DOM complet, mais évite le layout et le rendu de ce qui est hors écran.

// Défilement infini : un élément « sentinelle » à la fin + IntersectionObserver
afterNextRender(() => {
  const io = new IntersectionObserver(
    ([entry]) => entry.isIntersecting && this.loadNextPage(),
    { rootMargin: '0px 0px 200px 0px' },   // commence à charger 200 px plus tôt
  );
  io.observe(this.sentinel().nativeElement);
  this.destroyRef.onDestroy(() => io.disconnect());
});
LiveDéfilement infini avec une sentinelle
chargé : 50 / 1 000Éléments DOM : 0
#1Ana PopescuCluj-Napoca0,0
#2Mihai PetrescuTimișoara791,9
#3Ioana ConstantinSibiu1 583,8
#4Radu GeorgescuBucurești2 375,7
#5Elena MarinBrașov3 167,6
#6Andrei StanOradea3 959,5
#7Maria DumitruIași4 751,4
#8Vlad IonescuConstanța5 543,3
#9Cristina PopescuCluj-Napoca6 335,2
#10Bogdan PetrescuTimișoara7 127,1
#11Ana ConstantinSibiu7 919,0
#12Mihai GeorgescuBucurești8 710,9
#13Ioana MarinBrașov9 502,8
#14Radu StanOradea294,7
#15Elena DumitruIași1 086,6
#16Andrei IonescuConstanța1 878,5
#17Maria PopescuCluj-Napoca2 670,4
#18Vlad PetrescuTimișoara3 462,3
#19Cristina ConstantinSibiu4 254,2
#20Bogdan GeorgescuBucurești5 046,1
#21Ana MarinBrașov5 838,0
#22Mihai StanOradea6 629,9
#23Ioana DumitruIași7 421,8
#24Radu IonescuConstanța8 213,7
#25Elena PopescuCluj-Napoca9 005,6
#26Andrei PetrescuTimișoara9 797,5
#27Maria ConstantinSibiu589,4
#28Vlad GeorgescuBucurești1 381,3
#29Cristina MarinBrașov2 173,2
#30Bogdan StanOradea2 965,1
#31Ana DumitruIași3 757,0
#32Mihai IonescuConstanța4 548,9
#33Ioana PopescuCluj-Napoca5 340,8
#34Radu PetrescuTimișoara6 132,7
#35Elena ConstantinSibiu6 924,6
#36Andrei GeorgescuBucurești7 716,5
#37Maria MarinBrașov8 508,4
#38Vlad StanOradea9 300,3
#39Cristina DumitruIași92,2
#40Bogdan IonescuConstanța884,1
#41Ana PopescuCluj-Napoca1 676,0
#42Mihai PetrescuTimișoara2 467,9
#43Ioana ConstantinSibiu3 259,8
#44Radu GeorgescuBucurești4 051,7
#45Elena MarinBrașov4 843,6
#46Andrei StanOradea5 635,5
#47Maria DumitruIași6 427,4
#48Vlad IonescuConstanța7 219,3
#49Cristina PopescuCluj-Napoca8 011,2
#50Bogdan PetrescuTimișoara8 803,1
la suite se charge en arrivant ici…
Faites défiler la liste : quand la sentinelle de fin approche de la zone visible, 50 lignes de plus se chargent. Le nombre d’éléments DOM augmente à chaque page. C’est la différence avec le Virtual Scroll.

Quand OUI, quand NON

Virtualiser
  • Des milliers ou des millions de lignes, déjà en mémoire : logs, transactions, tableaux de données.
  • Des lignes de même hauteur (ou que vous pouvez ramener à la même hauteur).
  • Des listes que l’on fait souvent défiler, où la fluidité compte.
  • Des sélecteurs avec un très grand nombre d’options (un dropdown de 10 000 éléments).
Choisir autre chose
  • Moins de quelques centaines de lignes : @for avec track suffit et reste plus simple.
  • Les données arrivent déjà paginées du serveur : pagination ou défilement infini.
  • Un contenu que les utilisateurs recherchent avec Ctrl+F, ou qui doit être entièrement accessible : pagination, content-visibility.
  • Des cartes de hauteurs très différentes : content-visibility ou @defer sur les sections.

Pièges fréquents

  • Viewport sans hauteur : il s’étire à la taille de la liste, donc il rend tout, et il n’y a plus aucune virtualisation. Donnez-lui une hauteur fixe ou flex.
  • itemSize différent de la hauteur réelle (padding, bordure, line-height) : les lignes « sautent », des trous apparaissent, et scrollToIndex atterrit ailleurs.
  • Pas de trackBy sur des données qui se rechargent : tout le DOM visible est recréé à chaque mise à jour.
  • Le conteneur change de taille (un panneau se ferme, un onglet est activé) et le viewport ne le sait pas. Appelez checkViewportSize().
  • Défilement infini sans disconnect() sur l’observer : les callbacks s’exécutent après la destruction du composant.

Aide-mémoire

NOMBRE DE LIGNES       SOLUTION
< ~300                 @for simple avec track
300 – quelques milliers pagination ou défilement infini (données chargées par blocs) · content-visibility: auto
milliers – millions    CDK Virtual Scroll (ne rend que le visible + le buffer)

CDK
  imports              CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll, CdkVirtualForOf (@angular/cdk/scrolling)
  obligatoire          hauteur du viewport + itemSize = la hauteur réelle d’une ligne
  options              minBufferPx / maxBufferPx · templateCacheSize · appendOnly · orientation="horizontal"
  API                  scrollToIndex(i, 'smooth') · scrolledIndexChange · getRenderedRange() · checkViewportSize()
  hauteurs variables   autosize est dans @angular/cdk-experimental, pas prêt pour la production

PIÈGES  @for ne virtualise pas · sans trackBy, le DOM est recréé à chaque défilement · Ctrl+F ne trouve pas les lignes non rendues

Chap. 16 · 0 sur 4 terminées

Leçon terminée ?

Marquez-la : elle apparaîtra sur votre carte, dans le sommaire et sur la page d’accueil.

Se marque toute seule une fois la leçon lue en entier.

  1. Web Workers
  2. Listes géantes : virtual scroll
  3. Animations modernes et View Transitions
  4. Fuites mémoire : les trouver et les corriger
Voir toute votre carte

Les 75 pages · tapez pour filtrer

  • 01Nouveautés d’Angular 21 & 22Tous les changements importants de la v21 et de la v22, en bref, avec un lien vers la page qui explique chacun.Départ
  • 01Votre première application, pas à pasUne liste de lecture construite d’un composant vide jusqu’à une application terminée — signals, @for, inputs et outputs, un service et un formulaire, pas à pas.Départ
  • 01Décisions rapides« Où mettre ça ? », « Signal ou Observable ? », « Shared ou feature ? » — des réponses courtes aux questions de tous les jours.Départ
  • 01Questions d’entretien Angular60 vraies questions d’entretien pour profils junior, confirmé et senior, avec la réponse à donner, la relance et les signaux d’alerte. Plus un entretien blanc.Départ
  • 02Standalone et bootstrapComment une application démarre sans NgModule : bootstrapApplication, app.config.ts, imports dans le composant.Fondamentaux modernes
  • 02Data binding et événementsInterpolation, bindings de propriétés et d’attributs, événements avec $event et filtres de touches, et comment [( )] est construit — ce qui entre dans le DOM et ce qui en revient.Fondamentaux modernes
  • 02Control Flow : @if, @for, @switchLa syntaxe de blocs qui remplace *ngIf/*ngFor/ngSwitch, avec track, @empty et vérifications d’exhaustivité.Fondamentaux modernes
  • 02@let et les nouvelles expressions de templateVariables locales avec @let, fonctions fléchées, spread, regex, instanceof, commentaires dans les balises.Fondamentaux modernes
  • 02@defer — Lazy Loading dans les templatesChargez les composants lourds uniquement quand c’est nécessaire : triggers, prefetch, placeholder, loading, error.Fondamentaux modernes
  • 02Bindings de classe et de stylePourquoi [class.x] et [style.x] ont remplacé ngClass et ngStyle (avec migration automatique depuis la v21).Fondamentaux modernes
  • 02Styles des composants et encapsulation:host, les trois modes d’encapsulation, les thèmes avec des variables CSS, et pourquoi ::ng-deep est un dernier recours.Fondamentaux modernes
  • 02Pipes : quand et commentPipes pures vs impures, pipes personnalisées, et comment choisir entre une pipe, computed() et une méthode.Fondamentaux modernes
  • 03signal() et computed()Les primitives réactives : état modifiable, état dérivé, égalité, untracked et règles d’immutabilité.Signals et réactivité
  • 03effect() — quand OUI, quand NONEffets de bord synchronisés avec les Signals : localStorage, logs, API impératives. Et leurs pièges.Signals et réactivité
  • 03linkedSignal() — état dérivé modifiableUn signal qui se réinitialise quand sa source change, mais que vous pouvez aussi modifier à la main.Signals et réactivité
  • 03resource, rxResource, httpResourceDes données asynchrones sous forme de Signals : status, value, error, reload, annulation automatique. Stable depuis la v22.Signals et réactivité
  • 03debounced() — signals différésLa version « debounce » d’un signal, sans RxJS. Parfait pour la recherche.Signals et réactivité
  • 03Listes : rechercher, filtrer, trier, paginerUne chaîne de signals computed qui ne recalcule que ce qui a changé, avec les filtres dans l’URL pour qu’un lien rouvre la même vue.Signals et réactivité
  • 03Les Observables depuis zéroCe qu’est un Observable, subscribe et unsubscribe, les opérateurs que vous utilisez chaque semaine, froid ou chaud — et quand un signal suffit.Signals et réactivité
  • 03Signals ↔ RxJStoSignal, toObservable, takeUntilDestroyed, outputFromObservable — et quand RxJS reste le bon choix.Signals et réactivité
  • 04input() et output()Les inputs comme Signals (required, transform, alias) et les outputs typés. Les données descendent, les événements remontent.Composants : communication
  • 04model() — binding bidirectionnelLe « banana-in-a-box » [( )] avec une seule API. Quand l’utiliser, et quand input + output est préférable.Composants : communication
  • 04viewChild et contentChildLes queries comme Signals : viewChild, viewChildren, contentChild, contentChildren, l’option read.Composants : communication
  • 04Lifecycle et DestroyRefQuels hooks comptent encore à l’ère des Signals, afterNextRender, DestroyRef et ngOnChanges typé.Composants : communication
  • 04Projection de contenung-content avec select, plusieurs slots, contenu par défaut et le piège de la projection conditionnelle.Composants : communication
  • 04ng-template et ngTemplateOutletLes templates comme valeurs : les render props en Angular, contexte typé et personnalisation des composants.Composants : communication
  • 05Smart vs DumbContainer vs présentation : qui détient l’état, qui se contente d’afficher, et comment bien les séparer.Conception des composants
  • 05Composants génériques <T>Un DataTable<T> ou un Select<T> correctement typé, avec des templates pour les cellules.Conception des composants
  • 05Composants dynamiquesNgComponentOutlet, createComponent avec inputBinding/outputBinding, et un tableau de bord construit à partir d’une configuration.Conception des composants
  • 05Directives et composition d’hôteLes directives d’attribut, les métadonnées host: {} et la Directive Composition API (hostDirectives).Conception des composants
  • 05Contrôles de formulaire personnalisésFormValueControl (Signal Forms) vs ControlValueAccessor : une notation par étoiles, dans les deux variantes.Conception des composants
  • 05Change DetectionOnPush est la valeur par défaut depuis la v22, Zone.js a disparu depuis la v21. Ce qui déclenche le rendu et comment éviter les mauvaises surprises.Conception des composants
  • 06inject() et @Service()Le nouveau décorateur @Service() de la v22, inject() vs le constructeur, et le contexte d’injection.Dependency Injection
  • 06Providers et hiérarchie des injecteursRoot vs route vs composant, useClass/useValue/useFactory/useExisting, et les modificateurs optional/self/skipSelf.Dependency Injection
  • 06InjectionToken et provideX()Configuration typée, fonctions provideFeature(), makeEnvironmentProviders et initializers.Dependency Injection
  • 06injectAsync() — services en Lazy LoadingChargez un service lourd uniquement quand vous en avez besoin, avec prefetch à l’idle.Dependency Injection
  • 07Structure des dossierscore / shared / features, ce que contient chacun, et à quoi ressemble l’intérieur d’une feature.Architecture d’application
  • 07Shared vs featureQuand déplacer un composant dans shared et (surtout) quand NE PAS le faire. Une checklist concrète.Architecture d’application
  • 07Interfaces et modèlesInterface en ligne ou fichier séparé ? DTO vs modèle de domaine, type vs interface, où les ranger.Architecture d’application
  • 07State Management avec les SignalsStores simples à base de Signals, facades, état local vs global, et quand NgRx devient nécessaire.Architecture d’application
  • 07Guide de style et nommageLes conventions officielles actuelles : noms de fichiers sans suffixes, protected, readonly, ordre des membres.Architecture d’application
  • 07Barrels, alias et frontièresindex.ts, alias de chemins, API publique d’une feature et règles de dépendances vérifiables automatiquement.Architecture d’application
  • 07Erreurs : @boundary et ErrorHandlerLe nouveau bloc @boundary de la v22.2, l’ErrorHandler global, les erreurs HTTP et les erreurs de resource.Architecture d’application
  • 08Routing moderne et Lazy LoadingloadComponent, routes enfants, paramètres comme inputs et héritage des paramètres (le nouveau comportement par défaut de la v22).Routing
  • 08Guards et resolverscanActivate, canMatch, canDeactivate et resolve sous forme de fonctions, avec redirections via UrlTree / RedirectCommand.Routing
  • 09Signal Forms — les basesDes formulaires bâtis sur les Signals : form(), [formField], validation par schéma. Stable depuis la v22.Formulaires
  • 09Signal Forms — avancéValidation personnalisée et inter-champs, async/HTTP avec debounce, listes dynamiques, désactivé/masqué, schémas réutilisables.Formulaires
  • 09Reactive Forms typésFormGroup typé, nonNullable, FormArray — et leur cohabitation avec les Signal Forms.Formulaires
  • 10HttpClient, interceptors et couche d’APIHttpClient sans configuration (v21), Fetch par défaut (v22), interceptors fonctionnels et une couche d’API avec mapping des DTO.HTTP et données
  • 11Performance — une checklistCe qui compte vraiment : OnPush + Signals, track, @defer, routes en Lazy Loading, images, bundles et Hydration.Qualité et performance
  • 11Tests avec VitestVitest est le runner par défaut depuis la v21. TestBed, composants avec Signals, services, HTTP et harnesses.Qualité et performance
  • 11DevTools : regarder dans votre applicationChrome DevTools et Angular DevTools pas à pas : ce que montre chaque panneau, avec des exercices qui fonctionnent sur ce site.Qualité et performance
  • 11Angular AriaDes composants headless et accessibles que vous stylez comme vous voulez : tabs, accordion, menu, listbox...Qualité et performance
  • 12SSR, Prerendering et modes de renduQuand rendre sur le serveur, quand au build et quand uniquement dans le navigateur : RenderMode par route, app.routes.server.ts et les coûts réels.SSR et Hydration
  • 12Hydration : event replay et incrémentaleComment le navigateur reprend le HTML du serveur sans le re-rendre : Hydration complète, event replay, @defer (hydrate on …) et erreurs de mismatch.SSR et Hydration
  • 12Code compatible serveurwindow n’existe pas sur le serveur : PLATFORM_ID, afterNextRender, DOCUMENT, TransferState, le cache HTTP et PendingTasks.SSR et Hydration
  • 13Micro frontendsQuand découper une application en plusieurs déploiements vaut le coup, et quand c’est juste de la complexité : Native Federation, Module Federation, iframes, Web Components.Architecture à grande échelle
  • 13Web Components avec Angular ElementscreateCustomElement : un composant Angular utilisé comme balise HTML n’importe où, plus la communication entre applications via des événements et BroadcastChannel.Architecture à grande échelle
  • 13Monorepo, bibliothèques et frontièresUn workspace avec plusieurs applications et bibliothèques : ng generate library, Nx, points d’entrée secondaires, publication et règles de dépendances.Architecture à grande échelle
  • 13Architecture hexagonale (ports et adapters)Un domaine sans Angular, des ports sous forme de classes abstraites et des adapters interchangeables : HTTP, localStorage ou en mémoire, sans toucher à l’UI.Architecture à grande échelle
  • 14Les Signals sous le capotLe graphe réactif : producteurs et consommateurs, push-pull, pourquoi computed est lazy et sans glitch, le problème du diamant et quand les effects s’exécutent.Réactivité avancée
  • 14RxJS avancé : opérateurs d’ordre supérieurswitchMap, mergeMap, concatMap et exhaustMap sur un diagramme interactif, plus retry avec backoff, shareReplay et comment éviter les fuites.Réactivité avancée
  • 14NgRx SignalStoreState Management à grande échelle sur les Signals : withState, withComputed, withMethods, rxMethod, entités et hooks — et quand un store simple suffit.Réactivité avancée
  • 14Machines à états avec les signalsFini les états impossibles : une machine typée sur des signals, avec transitions, gardes et un diagramme d’états en direct.Réactivité avancée
  • 15WebSockets : un temps réel qui résiste au réseauUn socket typé sous forme de signals : reconnexion avec backoff et jitter, heartbeat, file d’attente hors ligne et sécurité SSR.Temps réel et hors ligne
  • 15Mises à jour optimistes et rollbackUne UI instantanée qui reste honnête : snapshot et rollback, réponses dans le désordre, clés d’idempotence.Temps réel et hors ligne
  • 15Offline-first : Service Worker et cacheLe Service Worker d’Angular : stratégies de cache, annonce des nouvelles versions et file d’attente pour les modifications faites hors ligne.Temps réel et hors ligne
  • 16Web WorkersDes calculs lourds sans bloquer l’UI : un vrai worker dans la page, l’échange de messages et quand ça n’en vaut pas la peine.UI avancée et performance
  • 16Listes géantes : virtual scroll100 000 lignes sans latence : le virtual scroll du CDK, itemSize, trackBy et les alternatives (pagination, scroll infini).UI avancée et performance
  • 16Animations modernes et View Transitionsanimate.enter et animate.leave à la place de @angular/animations (déprécié), transitions CSS et View Transitions à la navigation.UI avancée et performance
  • 16Fuites mémoire : les trouver et les corrigerAbonnements, timers et listeners qui survivent à leur composant — comment les repérer dans les DevTools et les fermer.UI avancée et performance
  • 17Sécurité : XSS, sanitisation et CSPCe qu’Angular fait automatiquement contre le XSS, quand bypassSecurityTrust devient dangereux, plus la CSP avec nonce, Trusted Types et XSRF.Sécurité et internationalisation
  • 17Authentification : tokens et refreshUn interceptor qui ajoute le token, le renouvelle une seule fois sur une 401 et rejoue les requêtes en attente — plus où conserver le token.Sécurité et internationalisation
  • 17OAuth 2.1 et OpenID Connect avec PKCELe flux authorization code avec PKCE, pas à pas : verifier et challenge, state et nonce, l’ID token, et pourquoi un BFF est plus sûr.Sécurité et internationalisation
  • 17Internationalisation (i18n)$localize, l’attribut i18n, ICU plural/select, un build par langue vs des traductions à l’exécution, et le formatage selon la locale.Sécurité et internationalisation