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.
switchMap est l'opérateur que tout reviewer Angular surveille.
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.
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.
map — transformer chaque valeurSens : « 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 }))
);
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)).
filter — ne garder que certaines valeursSens : « 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
);
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.
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.
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.
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é.
map dans un pipe ?switchMap est idéal pour une sauvegarde auto ou une recherche ?pipe, quand le travail des opérateurs s'exécute-t-il ?.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.
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.
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.