Leçon 09 · Phase 1 — Réactivité moderne RxJS · ~18 min

RxJS #2 : opérateurs (map, filter, switchMap)

Un Observable brut, c'est rarement ce que tu veux afficher : il faut le transformer, filtrer, parfois enchaîner sur un autre flux (un appel API). Ça se fait avec des opérateurs, branchés dans un .pipe(...). Trois suffisent pour 80 % des cas : map, filter, switchMap.

Pourquoi ça compte pour Alliance C'est ici que RxJS arrête de paraître verbeux et commence à supprimer du code. Un champ de recherche qui interroge l'API à chaque frappe sans la marteler, une sauvegarde auto qui annule la requête précédente, un flux de mesures filtré pour ne garder que les alertes : tout ça, c'est trois opérateurs. Sans eux, tu réécris à la main des timers, des drapeaux « requête en cours » et de l'annulation — le terrain des bugs de course. switchMap est l'opérateur que tout reviewer Angular surveille.

1. Le problème concret : sauver la note côté serveur

En leçon 07, l'auto-save de la note écrivait dans localStorage à chaque frappe (un effect). Maintenant on veut sauver sur le serveur. Frapper l'API à chaque touche serait absurde : il faut attendre une pause, ignorer les saisies vides, et si l'utilisateur repart à taper pendant qu'une sauvegarde est en vol, annuler celle-ci. Voilà le pipeline complet :

import { debounceTime, map, filter, switchMap } from 'rxjs';

// saisie$ = le flux des textes saisis (1 valeur par frappe).
// Comment le fabriquer depuis le champ : leçon 10 (toObservable).
this.saisie$.pipe(
  debounceTime(400),                  // attendre 400 ms de pause de frappe
  map(texte => texte.trim()),         // transformer : nettoyer
  filter(texte => texte.length > 0),  // garder : pas de sauvegarde vide
  switchMap(texte => this.api.sauver(texte)) // sauver, en ANNULANT la précédente
).subscribe(() => this.etat.set('enregistré ✓'));

Le .pipe(...) compose les opérateurs : chaque valeur traverse la chaîne de haut en bas. pipe ne modifie pas le flux d'origine — il renvoie un nouvel Observable (comme une chaîne LINQ ne modifie pas la liste source). Et tout reste paresseux : rien ne tourne tant que personne ne subscribe.

Ce que ça remplace (la vraie valeur) À la main, sans RxJS : un setTimeout que tu clearTimeout à chaque frappe (le debounce), un drapeau sauvegardeEnCours, l'AbortController pour couper la requête précédente, et la logique pour ignorer une réponse qui revient trop tard. ~30 lignes d'état fragile, sources classiques de bugs de course. Le pipe ci-dessus fait tout ça en 4 lignes déclaratives. C'est ça, le retour sur investissement de RxJS.

2. map — transformer chaque valeur

Sens : « pour chaque valeur qui passe, remplace-la par autre chose ». Relation 1 pour 1. On transforme une mesure brute en objet d'affichage :

// mesures$ : Observable<number> (leçon 08) → Observable<{ valeur, alerte }>
this.mesures$.pipe(
  map(c => ({ valeur: Math.round(c), alerte: c > 80 }))
);
Analogie LINQ map, c'est le .Select() de LINQ (ou Array.map en JS) — mais appliqué au fil du temps, à chaque valeur qui arrive, au lieu d'une collection figée. nombres.Select(n => n * n)flux$.pipe(map(n => n * n)).

3. filter — ne garder que certaines valeurs

Sens : « laisse passer la valeur seulement si la condition est vraie, sinon jette-la ». On dérive un flux qui n'émet que les alertes :

// ne réagir QUE quand une mesure dépasse le seuil
const alertes$ = this.mesures$.pipe(
  filter(c => c > 80)   // les valeurs ≤ 80 ne passent pas
);
Analogie LINQ filter, c'est le .Where() de LINQ (ou Array.filter). Même réflexe : postes.Where(p => p.Actif)postes$.pipe(filter(p => p.actif)). map change la valeur, filter décide si elle continue.

4. switchMap — enchaîner sur un autre flux (et annuler)

Les deux précédents restent dans le même flux. switchMap est différent : pour chaque valeur reçue, il démarre un nouvel Observable interne (typiquement un appel API) et renvoie ses valeurs. Le mot-clé, c'est « switch » : si une nouvelle valeur arrive alors que l'Observable interne précédent n'a pas fini, il l'annule et bascule sur le nouveau.

// à chaque texte stabilisé → on sauve ; une nouvelle frappe annule la sauvegarde en vol
switchMap(texte => this.api.sauver(texte))

C'est exactement ce qu'on veut pour une sauvegarde auto ou une recherche : seule la dernière saisie compte. Sans switchMap, deux sauvegardes peuvent revenir dans le désordre et tu écrases la récente par une ancienne (bug de course classique). switchMap garantit qu'une seule requête est vivante : la plus récente.

Analogie .NET switchMap, c'est comme relancer une Task et annuler la précédente via son CancellationToken dès qu'une nouvelle part. Tu ne gardes jamais deux opérations concurrentes : la dernière intention gagne, les vieilles sont coupées net. RxJS te donne ça gratuitement, sans gérer le token à la main.
Œil de tech lead (1) switchMap a des cousins, et choisir le mauvais est un vrai bug de revue : mergeMap (tout en parallèle, garde tout), concatMap (à la queue leu leu, dans l'ordre), exhaustMap (ignore les nouvelles tant qu'une est en cours — anti double-clic). Recherche / auto-save → switchMap. Envoi de formulaire à ne pas dupliquer → exhaustMap. Opérations à ne pas perdre ni désordonner → concatMap. (2) Les opérateurs sont purs et paresseux : pipe ne déclenche rien, seul subscribe (ou l'async pipe, leçon 10) lance la machine. (3) Un switchMap sur un appel HTTP gère l'annulation pour toi — ne réécris pas d'AbortController à côté.

5. Vérifie ta compréhension

Que fait l'opérateur map dans un pipe ?
Pourquoi switchMap est idéal pour une sauvegarde auto ou une recherche ?
Dans un pipe, quand le travail des opérateurs s'exécute-t-il ?
À retenir On transforme un Observable via .pipe(op1, op2, …), qui renvoie un nouveau flux (paresseux, source intacte). map = transformer chaque valeur (le Select de LINQ). filter = ne garder que certaines (le Where). switchMap = enchaîner sur un Observable interne (un appel API) en annulant le précédent — le bon choix pour recherche / auto-save. Compagnon utile : debounceTime (attendre une pause). Réflexe de lead : choisir le bon *Map (switch / merge / concat / exhaust) selon la politique d'annulation voulue.
Source primaire à lire (10 min)

RxJS — Operators (concept du pipe), puis la fiche de switchMap avec son diagramme à billes (marble diagram). Survole aussi mergeMap pour sentir la différence d'annulation.

Je suis ton prof — pose-moi des questions. Tu veux qu'on déroule le pipeline d'auto-save serveur de bout en bout (avec le champ réel et un faux api.sauver), ou que je te montre côte à côte switchMap vs mergeMap sur une recherche pour voir le bug de course apparaître ? Demande-moi, ici, maintenant.
◀ 08 · RxJS #1 : c'est quoi un Observable Suivant : 10 · RxJS #3 : Subjects, async pipe, toSignal ▶