Quand j’ai découvert RxJS il y a quelques années avec Angular, je voyais juste des .subscribe()
partout sur des trucs qu’on appelait des observables.
Mais ce que je ne voyais pas encore, c’étaient les fuites mémoire, les comportements bizarres, et les chaînes d’opérateurs bancales.
Avec le temps, j’ai compris que la programmation réactive, c’est bien plus qu’un outil pour gérer des appels API. C’est un changement de paradigme, qui implique aussi de bonnes pratiques et un modèle mental différent.
🔍 Mais avant tout, c’est quoi un Observable ?
Un observable est un flux de données asynchrone que l’on peut écouter dans le temps.
Il est au cœur de la programmation réactive avec RxJS.
🧠 L’idée de base : une source qui émet des valeurs
Imaginez un tuyau dans lequel des données arrivent au fil du temps :
const obs$ = new Observable(observer => {
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
});
Ici, on crée un flux qui émet 1
, 2
, 3
, puis s’arrête.
Pour “écouter” ce flux, on utilise subscribe()
:
obs$.subscribe(value => console.log(value));
// affiche : 1, 2, 3
🔁 Différence avec une Promise
Feature | Promise | Observable |
---|---|---|
Valeurs | Une seule | Plusieurs dans le temps |
Cancelable | ❌ non | ✅ oui |
Lazy (sur demande) | ✅ oui | ✅ oui |
Opérateurs | .then() , .catch() | .pipe() , map() , filter() … |
Exécution | Dès création | Au moment du subscribe() |
📦 Exemple concret : une recherche utilisateur
searchInput$.pipe(
debounceTime(300),
switchMap(query => this.api.search(query))
).subscribe(results => {
this.results = results;
});
searchInput$
est un observable d’événements clavier : on récupère les modifications du champ “Rechercher un utilisateur”debounceTime(300)
: attend une pause de 300ms : on veut éviter de lancer une recherche à chaque fois qu’un utilisateur appuie sur une touche. Ledebounce
ici attend 300ms après la dernière valeur émise pour continuer le fluxswitchMap(...)
: on vient switcher d’observable dans le flux :this.api.search(query)
va instancier un nouvel observable (une requête http) qu’on a très envie d’attendre pour récupérer son résultat. On switch donc de fluxsubscribe(...)
: réceptionne les résultats et met à jour l’UI (en vrai, si vous faîtes du Angular, je préfère le pipe| async
)
💡 Un observable peut :
- émettre 0, 1 ou plusieurs valeurs
- être infini (par exemple :
fromEvent
,interval
) - être froid ou chaud (voir la section suivante)
- être combiné, transformé, filtré, temporisé, réessayé, etc.
RxJS fournit plus de 60 opérateurs pour composer des flux de manière expressive et lisible.
🌡️ Observable froid vs chaud : comprendre la différence
🔹 Observable froid
Un observable est dit “froid” quand la source est réévaluée à chaque abonnement. Cela signifie que chaque souscripteur a son propre cycle de vie.
const cold$ = new Observable(observer => {
console.log('Nouvel abonné');
observer.next(Math.random());
});
cold$.subscribe(val => console.log('Abonné 1 :', val));
cold$.subscribe(val => console.log('Abonné 2 :', val));
Nouvel abonné
Abonné 1 : 0.42
Nouvel abonné
Abonné 2 : 0.88
💡 Typiquement : http.get()
, of()
, from()
…
🔸 Observable chaud
Un observable est dit “chaud” quand il partage sa source de données entre les abonnés.
const subject = new Subject();
subject.subscribe(val => console.log('Abonné 1 :', val));
subject.next(1);
subject.next(2);
subject.subscribe(val => console.log('Abonné 2 :', val));
subject.next(3);
Abonné 1 : 1
Abonné 1 : 2
Abonné 1 : 3
Abonné 2 : 3
💡 Typiquement : Subject
, fromEvent
, WebSocket
, etc.
🔄 Froid + partage = chaud (avec shareReplay
)
Il est possible de rendre un observable froid “chaud” en le partageant avec share()
ou shareReplay()
:
const api$ = this.http.get('/data').pipe(shareReplay(1));
Cela évite de déclencher plusieurs requêtes HTTP si plusieurs abonnés s’inscrivent.
✅ Les bonnes pratiques à adopter
Un grand pouvoir implique de grandes responsabilité, comme dirait l’oncle.
1. Toujours gérer la désinscription
Un subscribe()
sans unsubscribe()
= fuite mémoire assurée.
Par défaut les souscriptions aux observables n’entraînent pas de désinscription automatique (comme les évènements JS en fait).
✅ À faire :
-
Utiliser
takeUntil()
outake(1)
: l’opérateur vient compléter l’observable automatiquementtakeUntil(notifer$)
Il fonctionne en écoutant un autre observable
notifier$
qui, lorsqu’il émet, provoque la complétion (et donc la désinscription) de la source observable.Il ne déclenche pas l’unsubscribe tant que ce notifier n’émet pas, mais une fois que c’est fait, la désinscription est automatique.
take(1)
complète automatiquement l’observable après la première émission, ce qui entraîne un unsubscribe automatique pour cet abonnement.
-
Désinscrire
unsubscribe()
dansngOnDestroy
de votre composant -
Utiliser
async
pipe dans les templates Angular: Leasync
pipe s’occupe de tout : il souscrit ET désinscrit automatiquement.
<div *ngIf="data$ | async as data">
{{ data.title }}
</div>
2. Éviter les souscriptions imbriquées
Ne faites pas ça 👇
this.a$.subscribe(a => {
this.b$.subscribe(b => {
// 😬
});
});
Utilisez switchMap
, mergeMap
, concatMap
, selon le comportement voulu :
this.result$ = this.a$.pipe(
switchMap(a => this.b$)
);
Pourquoi ?
- Parce qu’il faut trois boîtes d’Aspirine pour lire une pyramide de la mort (tu sais, quand tu as un paquet de
subscribe()
imbriqués… - La gestion des erreurs devient compliquée : chaque
subscribe()
gère ses propres erreurs. L’imbrication = acrobaties assurées pour propagées ou centraliser les erreurs correctement - Fuites mémoires garanties
- Ordre d’exécution non maîtrisé
3. Choisir le bon opérateur
Il en existe un bon gros paquet, alors n’hésitons pas à prendre le plus pertinent ! Quelques-uns que j’utilise le plus :
-
switchMap()
: annule l’observable précédent à chaque nouvelle émission👉 Utile pour des recherches utilisateur en temps réel
-
debounceTime(500)
: attend un délai de calme avant d’émettre👉 Utile pour réduire le bruit sur les champs de recherche
-
map(val => val * 2)
: transforme les valeurs👉 Manipulation de données simples
-
throttleTime(2000)
: ignore les émissions trop rapprochées👉 Anti-spam sur bouton de soumission
-
shareReplay(1)
: partage et met en cache les dernières valeurs👉 Pour éviter de rappeler une API 5 fois pour 5 abonnés
🔗 Pour aller plus loin : learnrxjs.io
⚠️ Attention à penser à long terme :
RxJS est un outil extrêmement puissant, mais sans bonnes pratiques, il peut rapidement devenir difficile à gérer.
Avant de plonger dans le code, prenez le temps de réfléchir à :
- La “température” de vos observables : distinguez bien les observables “froids” (qui produisent des données à chaque abonn
- *ement) des “chauds” (qui émettent indépendamment des abonnés).
- Qui s’abonne, quand et pourquoi : comprenez bien le cycle de vie de vos abonnements pour éviter les fuites mémoire.
- Comment gérer l’annulation : prévoyez toujours comment et quand vos abonnements doivent être nettoyés (désabonnés).
- La robustesse de vos flux : imaginez vos chaînes d’opérateurs capables de gérer les erreurs, les interruptions, et même d’évoluer sans casser l’application (par exemple avec des rollbacks logiques).
Enfin, testez vos flux RxJS indépendamment des composants pour garantir leur fiabilité et faciliter la maintenance.
En résumé
RxJS n’est pas juste un outil pour “faire des appels API” ou “écouter des événements”.
C’est une manière structurée et déclarative de penser la gestion des flux de données asynchrones.
Et comme tout outil puissant mal utilisé… ça peut devenir un cauchemar.
Et vous, quels sont vos opérateurs RxJS préférés ?