AngularAtlaséd. 22.2

Chap. 15Temps réel et hors ligne65 / 75

WebSockets : un temps réel qui résiste au réseau

Un socket typé sous forme de signals : reconnexion avec backoff et jitter, heartbeat, file d’attente hors ligne et sécurité SSR.

architecture ~0 min de lecture

HTTP, c’est une question puis une réponse. Un WebSocket, c’est une ligne qui reste ouverte, et le serveur peut parler en premier : un nouveau message de chat, un prix qui a changé, un build terminé. Ouvrir la ligne tient en une ligne de code. La garder en vie dans un train, sur un portable qui se met en veille ou pendant un déploiement du serveur, voilà le vrai travail.

Cette page construit un socket typé qui se reconnecte tout seul et s’expose sous forme de signals : l’état de la connexion, les messages, une file d’envoi pour ce que vous envoyez hors ligne, et une reconnexion qui ne renverse pas le serveur dès qu’il revient.

Pourquoi c’est important
En production, les sockets meurent tout le temps : le Wi-Fi change de réseau, les proxys ferment les connexions inactives au bout d’une minute, les déploiements redémarrent les serveurs. Un socket sans reconnexion paraît parfait en démo, puis cesse en silence de se mettre à jour entre les mains de l’utilisateur — sans la moindre erreur à l’écran.

Le minimum : webSocket() de RxJS

RxJS fournit un subject pour les WebSockets : subscribe() ouvre la connexion, next() envoie, unsubscribe() la ferme. Par défaut, les messages sont en JSON dans les deux sens (serializer et deserializer changent cela).

import { webSocket } from 'rxjs/webSocket';

interface ChatMessage {
  type: 'chat';
  user: string;
  text: string;
}

const socket = webSocket<ChatMessage>('wss://api.example.com/live');

// subscribe() ouvre la connexion ; chaque message arrive déjà parsé (JSON.parse)
const sub = socket.subscribe({
  next: (msg) => console.log(msg.user, msg.text),
  error: (err) => console.error('connexion perdue', err), // le flux SE TERMINE ici
  complete: () => console.log('fermée proprement par le serveur'), // ...ou ici
});

socket.next({ type: 'chat', user: 'ana', text: 'hi' }); // envoyé comme JSON.stringify(...)

sub.unsubscribe(); // ferme la connexion

Deux détails comptent pour la suite. Le flux se termine quand la connexion tombe : avec error si c’était anormal, avec complete si le serveur l’a fermée proprement. Et ce que vous envoyez avant l’ouverture est mis en tampon puis envoyé à la connexion.

Une connexion qui revient : backoff avec jitter

Quand la ligne tombe, réessayer immédiatement en boucle est la pire option. Si le serveur vient de redémarrer, tous les clients le frappent à la même seconde — le thundering herd — et il retombe. La recette habituelle : attendre plus longtemps après chaque échec (backoff exponentiel), plafonner l’attente et ajouter du hasard (jitter) pour que les clients s’étalent.

/**
 * « Full jitter » : une attente aléatoire entre 0 et min(cap, base · 2^attempt).
 * tentative 1 → 0–1 s, 2 → 0–2 s, 3 → 0–4 s ... jamais plus de 10 s.
 */
export function fullJitter(attempt: number, base = 500, cap = 10_000): number {
  return Math.random() * Math.min(cap, base * 2 ** attempt);
}
Live200 clients se reconnectent après un redémarrage du serveur
retour du serveur 0 s 10 s 20 s 30 s 40 s

tentative échouéereconnecté

pic après redémarrage
200
tentatives au total
2200
reconnectés
200 / 200
dernier retour à
10.2 s
Comparez les trois stratégies. Regardez le pic : combien de clients frappent le serveur dans la même demi-seconde, juste après son retour.

Fixe 1 s martèle le serveur tant qu’il est tombé. Exponentiel sans jitter envoie moins de requêtes, mais par vagues synchronisées : ceux qui ont échoué au même moment réessaient au même moment. Full jitter étale les retours sur plusieurs secondes, le serveur reçoit un filet au lieu d’un mur ; le prix, c’est que les clients les moins chanceux reviennent quelques secondes plus tard. Le plafond garde cette attente raisonnable. Pour un socket, c’est presque toujours le bon compromis.

retry() ne suffit pas : les fermetures propres

retry() ne réagit qu’aux erreurs. Un serveur qui redémarre poliment ferme les connexions avec le code 1001 ou 1012 (« service restart ») et wasClean: true — et le flux se termine, sans erreur. Sans repeat(), votre application reste déconnectée précisément pendant les déploiements. Remettez le compteur de tentatives à zéro dans openObserver, quand la ligne est vraiment ouverte, pas au premier message.

import { repeat, retry, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';
import type { ServerMessage } from './live-socket';

let attempt = 0;
const wait = () => timer(fullJitter(++attempt));

const socket = webSocket<ServerMessage>({
  url: 'wss://api.example.com/live',
  openObserver: { next: () => (attempt = 0) }, // remise à zéro quand la ligne est vraiment ouverte
});

socket
  .pipe(
    retry({ delay: wait }), // erreur réseau, fermeture anormale (1006)
    repeat({ delay: wait }), // fermeture PROPRE (1001, 1012 « service restart ») : le flux se termine !
  )
  .subscribe((msg) => console.log(msg));
Le tampon sur lequel on ne peut pas compter
WebSocketSubject garde ce que vous envoyez avant l’ouverture du socket, mais remplace ce tampon à chaque tentative échouée (_resetState() dans le code source de RxJS). Les messages envoyés pendant la coupure sont perdus dès qu’une seule tentative de reconnexion échoue. S’ils comptent, gardez votre propre file d’envoi et videz-la dans openObserver.

Le socket comme service avec des signals

Les composants ne devraient rien savoir du backoff ni du JSON. Un service garde une seule connexion pour toute l’application, expose l’état et les messages sous forme de signals et offre un seul send(). Tant que la ligne est coupée, les messages attendent dans une file bornée et partent dans l’ordre après la reconnexion.

import { DestroyRef, Service, inject, signal } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval, repeat, retry, timeout, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';

export type ServerMessage =
  | { type: 'chat'; user: string; text: string }
  | { type: 'presence'; online: number }
  | { type: 'pong' };
export type ClientMessage = { type: 'chat'; text: string } | { type: 'ping' };

export type SocketStatus = 'idle' | 'connecting' | 'open' | 'reconnecting';

const PING_EVERY = 15_000;
const DEAD_AFTER = 20_000; // aucun message pendant 20 s = connexion morte

/** Une seule connexion pour toute l’application, exposée sous forme de signals. */
@Service()
export class LiveSocket {
  private readonly destroyRef = inject(DestroyRef);

  readonly status = signal<SocketStatus>('idle');
  readonly online = signal(0);
  readonly messages = signal<readonly ServerMessage[]>([]);
  /** Ce qui a été envoyé pendant la coupure ; part, dans l’ordre, au prochain « open ». */
  readonly outbox = signal<readonly ClientMessage[]>([]);

  private attempt = 0;
  // RxJS utilise un seul type pour les deux sens
  private readonly socket = webSocket<ServerMessage | ClientMessage>({
    url: 'wss://api.example.com/live',
    openObserver: { next: () => this.opened() },
    closeObserver: { next: () => this.status.set('reconnecting') },
  });

  connect(): void {
    if (this.status() !== 'idle') return;
    this.status.set('connecting');
    const wait = () => timer(fullJitter(++this.attempt));

    this.socket
      .pipe(
        timeout({ each: DEAD_AFTER }), // heartbeat : silence → erreur → retry
        retry({ delay: wait }),
        repeat({ delay: wait }),
        takeUntilDestroyed(this.destroyRef),
      )
      .subscribe((msg) => this.receive(msg as ServerMessage));

    interval(PING_EVERY)
      .pipe(takeUntilDestroyed(this.destroyRef))
      .subscribe(() => {
        if (this.status() === 'open') this.socket.next({ type: 'ping' });
      });
  }

  send(msg: ClientMessage): void {
    if (this.status() === 'open') this.socket.next(msg);
    else this.outbox.update((list) => [...list, msg].slice(-100)); // borné !
  }

  private opened(): void {
    this.status.set('open');
    this.attempt = 0;
    const pending = this.outbox();
    this.outbox.set([]);
    pending.forEach((msg) => this.socket.next(msg));
  }

  private receive(msg: ServerMessage): void {
    if (msg.type === 'presence') this.online.set(msg.online);
    else if (msg.type === 'chat') this.messages.update((list) => [...list, msg].slice(-200));
  }
}
LiveUne connexion en direct : coupez le réseau, redémarrez le serveur
non connecté0 en ligne
  1. Écrivez un message : le serveur le renvoie à tous les connectés.
  1. Les événements de la connexion apparaissent ici.
Coupez le réseau, envoyez deux messages, puis rétablissez-le : ils partent dans l’ordre après la reconnexion. Essayez ensuite « Coupure silencieuse » : la connexion reste ouverte, mais plus rien ne passe.

La démo exécute le code ci-dessus avec des intervalles plus courts. Seul WebSocketCtor est remplacé : au lieu d’un vrai WebSocket, il reçoit un serveur en mémoire qui peut redémarrer, perdre le réseau ou figer une connexion.

Heartbeat : une connexion morte qui a l’air vivante

Une connexion peut mourir sans aucun événement de fermeture : on rabat l’écran du portable, un NAT oublie la correspondance, le réseau mobile change de cellule. TCP peut ne rien remarquer pendant des minutes, et readyState dit toujours OPEN. Le remède est un heartbeat : le client envoie un ping à intervalle fixe, et timeout({ each }) traite tout silence plus long que la limite comme une ligne morte. L’erreur va à retry(), qui ouvre une nouvelle connexion.

Ce que vous envoyez sur une ligne morte mais ouverte est perdu sans erreur : send() réussit, les données n’arrivent jamais. Pour les messages qui doivent arriver (une commande, un paiement), donnez un id à chacun, attendez un ack du serveur et renvoyez après la reconnexion ce qui n’a pas été acquitté. Le serveur ignore les doublons par id.

SSR, authentification, onglets et resync

  • SSR et prerendering. Le code tourne aussi sur le serveur, où aucun socket ne vaut la peine d’être ouvert. Connectez-vous dans afterNextRender (ou derrière isPlatformBrowser) et laissez le rendu serveur afficher un état vide. Voir code compatible serveur.
  • Authentification. Le navigateur ne vous laisse pas mettre d’en-tête Authorization sur un WebSocket. Options : un cookie de session (même site), un ticket court à usage unique obtenu en HTTP et placé dans l’URL, ou un premier message auth. Pas de token à longue durée de vie dans l’URL — les URL finissent dans les logs du serveur. Les tokens et le refresh sont traités dans Authentification : tokens et refresh.
  • Plusieurs onglets. Chaque onglet ouvre sa propre connexion. Si c’est un problème (limites de connexions, notifications en double), un onglet peut posséder le socket et transmettre les messages aux autres via BroadcastChannel ou un SharedWorker.
  • Resync après reconnexion. Pendant votre absence, le serveur a envoyé des choses que vous n’avez jamais reçues. Après chaque open, demandez ce que vous avez manqué (since=lastEventId) ou rechargez l’état en HTTP. Les modifications faites entre-temps sont le sujet des mises à jour optimistes.
import { HttpClient } from '@angular/common/http';
import { defer, retry, switchMap, timer } from 'rxjs';
import { webSocket } from 'rxjs/webSocket';
import { fullJitter } from './backoff';
import type { ServerMessage } from './live-socket';

// Le navigateur ne peut pas mettre d’en-tête Authorization sur un WebSocket.
// 1) un ticket court via HTTP (votre intercepteur d’auth ajoute le token comme d’habitude)
// 2) le ticket est à usage unique et vit ~30 s, il peut donc aller dans l’URL
// 3) defer() → chaque reconnexion demande un NOUVEAU ticket
export function authenticatedSocket(http: HttpClient) {
  return defer(() => http.post<{ ticket: string }>('/api/ws-ticket', {})).pipe(
    switchMap(({ ticket }) => webSocket<ServerMessage>(`wss://api.example.com/live?ticket=${ticket}`)),
    retry({ delay: (_, n) => timer(fullJitter(n)) }),
  );
}

WebSocket ou Server-Sent Events ?

Si seul le serveur parle — notifications, un fil d’actualité, la progression d’une tâche, du texte envoyé token par token —, les Server-Sent Events sont plus simples : du HTTP ordinaire, une reconnexion intégrée au navigateur et l’id du dernier événement renvoyé automatiquement.

WebSocketServer-Sent Events
Sensdans les deux sensserveur → navigateur
Protocolele sien (ws:// / wss://, après un upgrade HTTP)HTTP ordinaire (text/event-stream)
Reconnexionà écrire vous-mêmeintégrée, avec Last-Event-ID
Donnéestexte et binairetexte uniquement
Usage typiquechat, édition collaborative, jeux, trading dans les deux sensnotifications, flux en direct, progression de tâches, streaming de texte IA
import { Observable } from 'rxjs';

/** Server-Sent Events : serveur → navigateur uniquement, HTTP simple, reconnexion intégrée. */
export function serverEvents<T>(url: string): Observable<T> {
  return new Observable<T>((subscriber) => {
    const source = new EventSource(url, { withCredentials: true });
    source.onmessage = (e) => subscriber.next(JSON.parse(e.data) as T);
    source.onerror = () => {
      // le navigateur se reconnecte seul (et envoie Last-Event-ID) ; seul CLOSED est définitif
      if (source.readyState === EventSource.CLOSED) subscriber.error(new Error('SSE closed'));
    };
    return () => source.close();
  });
}

Quand OUI, quand NON

WebSocket
  • Les deux côtés parlent souvent : chat, présence, édition collaborative.
  • La latence compte, et les messages sont petits et fréquents.
  • Il vous faut des données binaires ou votre propre protocole sur la connexion.
Autre chose
  • Seul le serveur parle : les Server-Sent Events sont plus simples. Des requêtes de temps en temps : un simple HttpClient.
  • Des données qui changent toutes les quelques minutes : un polling toutes les 30–60 s coûte moins cher et s’exploite bien plus simplement.
  • Des modifications qui doivent marcher hors ligne et se synchroniser plus tard : c’est un problème offline-first, pas un problème de socket.

Pièges fréquents

  • Un socket par composant. Cinq widgets, cinq connexions vers le même serveur. Gardez la connexion dans un service root.
  • Retry sans jitter ou sans plafond. Des milliers de clients reviennent par vagues synchronisées, ou un client attend 17 minutes après une longue panne.
  • Seulement retry(), sans repeat(). Les fermetures propres (à chaque déploiement) terminent le flux pour de bon.
  • Compter sur le tampon intégré. Les messages envoyés hors ligne disparaissent après la première tentative échouée. Utilisez votre propre file d’envoi, bornée.
  • Pas de heartbeat. L’état dit « connecté », mais rien n’est arrivé depuis dix minutes.
  • Se connecter pendant le SSR, ou un long JWT dans l’URL. Le premier ouvre des sockets depuis le serveur ; le second laisse des tokens dans les logs.

Aide-mémoire

webSocket<T>(url | config)        subscribe = connexion · next() = envoi · unsubscribe = fermeture
avant le premier « open »         next() est mis en tampon et envoyé à la connexion
...mais après un échec            ce tampon est jeté → gardez votre propre file d’envoi
erreur réseau / code 1006         retry({ delay })
fermeture propre (1000/1001/1012) repeat({ delay })   le flux SE TERMINE, sans erreur
backoff                           random(0, min(cap, base · 2^n))   « full jitter »
remettre le backoff à zéro        dans openObserver, pas au premier message
morte mais pas fermée             ping toutes les 15 s + timeout({ each: 20_000 })
SSR / prerender                   connexion dans afterNextRender, jamais dans un constructeur
auth                              cookie · ticket court à usage unique · premier message « auth »
après reconnexion                 resync : demander ce qu’on a manqué (since=lastEventId)
messages qui doivent arriver      id + ack du serveur, renvoyer ce qui n’est pas acquitté
un seul sens ?                    Server-Sent Events (EventSource) : la reconnexion est intégrée

Chap. 15 · 0 sur 3 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. WebSockets : un temps réel qui résiste au réseau
  2. Mises à jour optimistes et rollback
  3. Offline-first : Service Worker et cache
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