# RxJS dans Angular 2026 : operators, Subjects et interop Signals
> RxJS dans Angular 2026 : maîtriser les operators, les Subjects et les patterns d'interop Signals utilisés en production, ainsi que les questions d'entretien les plus fréquentes.
- Published: 2026-07-02
- Updated: 2026-07-07
- Author: SharpSkill
- Tags: angular, rxjs, signals, observables, typescript, deep-dive
- Reading time: 10 min
---
RxJS reste la colonne vertébrale de la programmation asynchrone dans Angular en 2026, même si les Signals transforment la façon dont les composants suivent leur état synchrone. Savoir quand recourir à un Observable, quand un Signal convient mieux, et comment les deux interagissent constitue désormais une compétence centrale pour tout développeur Angular. Ce guide approfondi parcourt les operators, les Subjects et les patterns d'interop qui distinguent une réponse d'entretien assurée d'une réponse hésitante.
> **Signals contre Observables en une ligne**
>
> Les Signals modélisent un état synchrone qui possède toujours une valeur actuelle ; les Observables modélisent des flux asynchrones d'événements dans le temps. Angular 20 conserve les deux, et le package `@angular/core/rxjs-interop` fournit le pont officiel entre eux.
## Pourquoi RxJS compte toujours aux côtés des Angular Signals
L'arrivée des Signals a poussé de nombreuses équipes à se demander si RxJS était en voie de disparition. Trois versions majeures plus tard, la réponse est non. Les Signals excellent à représenter un état qui possède une valeur à l'instant présent : un compteur, un champ de formulaire, un onglet sélectionné. Les Observables excellent à modéliser des événements qui arrivent au fil du temps et qui peuvent nécessiter transformation, annulation ou combinaison : réponses HTTP, messages WebSocket, saisie clavier, événements du router et timers.
Les propres API d'Angular rendent cette séparation visible. `HttpClient` retourne toujours des Observables. Les formulaires réactifs exposent `valueChanges` et `statusChanges` sous forme d'Observables. Les événements du router circulent à travers un Observable. Partout où plusieurs événements asynchrones transitent par le même canal, les operators RxJS demeurent l'outil le plus expressif disponible. Le [guide officiel de réactivité Angular](https://angular.dev/guide/signals/rxjs-interop) traite les deux modèles comme complémentaires plutôt que concurrents, et la [documentation RxJS](https://rxjs.dev/guide/overview) reste une référence obligatoire pour tout flux de données non trivial.
La règle pratique qui tient en revue de code : recourir aux Signals pour l'état local du composant lu dans les templates, et aux Observables pour les pipelines asynchrones qui ont besoin d'operators. La couche d'interop relie les deux afin qu'aucun modèle ne devienne un mur.
Les operators de combinaison renforcent ce constat. Coordonner plusieurs sources asynchrones, comme fusionner les paramètres de route avec les filtres enregistrés d'un utilisateur et un flux de prix en direct, correspond exactement à ce pour quoi `combineLatest`, `withLatestFrom` et `forkJoin` ont été conçus. Les Signals peuvent dériver des valeurs calculées à partir d'autres Signals, mais ils ne peuvent pas exprimer une coordination temporelle telle que « n'émettre qu'après que les trois sources aient produit au moins une valeur ». Cette composition déclarative dans le temps est la raison durable pour laquelle RxJS reste dans la boîte à outils.
## Les operators RxJS essentiels dont chaque développeur Angular dépend
Les operators sont des fonctions pures qui prennent un Observable et en retournent un nouveau. Le scénario d'entretien classique est la recherche à la volée, car il met en jeu quatre operators d'un coup : limiter la saisie, ignorer les doublons, annuler les requêtes obsolètes et récupérer après une erreur.
```typescript
// search.component.ts
import { Component, inject } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, switchMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
selector: 'app-search',
imports: [ReactiveFormsModule],
template: ``,
})
export class SearchComponent {
private http = inject(HttpClient);
query = new FormControl('');
results$ = this.query.valueChanges.pipe(
debounceTime(300), // wait until typing pauses for 300ms
distinctUntilChanged(), // ignore emissions that repeat the last value
switchMap((term) => // cancel the in-flight request, start a fresh one
this.http.get(`/api/search?q=${term}`).pipe(
catchError(() => of([])) // recover from a failed request without killing the stream
)
)
);
}
```
Le détail subtil que sondent les recruteurs est la raison pour laquelle `switchMap` se situe au niveau externe tandis que `catchError` est imbriqué à l'intérieur. Placer `catchError` sur l'Observable HTTP interne signifie qu'une requête échouée retourne un tableau vide et que le flux externe continue de circuler. Déplacer `catchError` vers le pipe externe mettrait fin à toute la recherche après la première erreur, cassant chaque frappe suivante. Les quatre operators d'aplatissement répondent chacun à une question de concurrence différente, et choisir le mauvais est l'une des sources les plus fréquentes de race conditions dans les applications Angular.
> **Choisir un operator d'aplatissement**
>
> Utiliser `switchMap` pour annuler la requête interne précédente et ne conserver que la plus récente (recherche typeahead). Utiliser `mergeMap` pour exécuter chaque requête interne en parallèle (uploads indépendants). Utiliser `concatMap` pour mettre les requêtes en file dans un ordre strict (écritures séquentielles qui ne doivent pas se chevaucher). Utiliser `exhaustMap` pour ignorer les nouvelles entrées jusqu'à la fin de l'actuelle (bloquer un bouton de soumission double-cliqué).
Faire ce choix correctement transforme une fonctionnalité instable en une fonctionnalité prévisible. Le [module RxJS operators de SharpSkill](/technologies/angular/interview-questions/rxjs-operators) travaille ces distinctions avec des questions notées qui reproduisent les race conditions exactes que chaque operator prévient.
## Subjects et multicasting : BehaviorSubject, ReplaySubject et Subject
Un Observable simple est unicast : chaque abonné déclenche une nouvelle exécution. Un Subject est à la fois un Observable et un Observer, ce qui le rend multicast. Cette propriété fait des Subjects la manière standard de construire un petit store d'état ou un bus d'événements au sein d'un service.
Les trois variantes diffèrent par ce qu'elles rejouent aux abonnés tardifs. Un `Subject` simple n'émet rien du passé, si bien qu'un abonné ne voit que les valeurs arrivant après son abonnement. Un `BehaviorSubject` stocke la valeur courante et la rejoue immédiatement, d'où la nécessité d'une valeur initiale. Un `ReplaySubject` met en tampon un nombre configurable d'émissions passées et les rejoue toutes.
```typescript
// cart.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class CartService {
// BehaviorSubject holds the latest value and replays it to new subscribers
private readonly itemsSubject = new BehaviorSubject([]);
// expose a read-only stream, never the writable Subject itself
readonly items$ = this.itemsSubject.asObservable();
add(item: string): void {
const current = this.itemsSubject.value; // synchronous access to the last value
this.itemsSubject.next([...current, item]); // emit the next immutable state
}
}
```
Deux habitudes comptent ici. D'abord, exposer `asObservable()` plutôt que le Subject empêche le code extérieur d'appeler `next()` et de muter l'état par la porte de derrière. Ensuite, émettre une nouvelle référence de tableau au lieu de muter sur place garde la détection de changement et les vérifications d'égalité prévisibles. Ce pattern précède les Signals et fonctionne toujours bien pour un état partagé et injectable que plusieurs composants sans lien consomment.
Cela dit, un `BehaviorSubject` utilisé uniquement pour détenir une valeur courante unique lue dans les templates s'exprime aujourd'hui souvent mieux avec un Signal modifiable, qui supprime l'abonnement et le cérémonial de `asObservable()`. Le Subject reste le meilleur choix quand le store doit aussi transformer, débouncer ou combiner ses émissions avec d'autres flux, puisque ces operators n'ont pas d'équivalent direct côté Signal. Trancher entre les deux au cas par cas, plutôt que d'en imposer un partout, est une marque de maîtrise que les recruteurs récompensent.
## Interop RxJS et Signals avec toSignal et toObservable
Le package d'interop livre deux fonctions phares. `toSignal` convertit un Observable en un Signal en lecture seule qu'un template peut consommer directement, et il gère automatiquement le cycle de vie de l'abonnement. `toObservable` fait l'inverse, transformant un Signal en Observable afin que les operators RxJS puissent traiter ses changements.
```typescript
// dashboard.component.ts
import { Component, signal, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
import { switchMap } from 'rxjs/operators';
@Component({ selector: 'app-dashboard', template: `{{ user()?.name }}` })
export class DashboardComponent {
private http = inject(HttpClient);
// a Signal drives the query; toObservable turns each change into a stream event
readonly userId = signal(1);
private readonly user$ = toObservable(this.userId).pipe(
switchMap((id) => this.http.get<{ name: string }>(`/api/users/${id}`))
);
// toSignal subscribes immediately and unsubscribes on destroy, no manual teardown
readonly user = toSignal(this.user$, { initialValue: null });
}
```
L'option `initialValue` mérite l'attention. Sans elle, `toSignal` retourne un Signal typé comme la valeur ou `undefined`, car rien n'a encore émis. Passer `initialValue: null` donne au template un état de départ défini et un type plus propre. Pour les sources synchrones telles qu'un `BehaviorSubject`, `requireSync: true` garantit qu'une valeur est disponible à la première lecture et retire entièrement `undefined` du type. Ce pont bidirectionnel est ce qui permet à une base de code d'adopter les Signals de manière incrémentale sans réécrire chaque pipeline Observable existant. Le [module d'entretien Angular Signals](/technologies/angular/interview-questions/angular-signals) couvre le modèle de réactivité en profondeur, et l'[aperçu des fonctionnalités Signals d'Angular 18](/blog/angular/angular-18-signals-new-features) retrace l'évolution de ces primitives.
## Relier les Observables aux ressources signal avec rxResource
Angular 20 met en avant `rxResource` comme la façon déclarative de charger des données asynchrones depuis un Observable vers un état basé sur les signals. Il suit les statuts de chargement, d'erreur et de résolution sous forme de signals, ce qui supprime la majeure partie de la gestion manuelle d'abonnement qu'exigeaient autrefois les appels HTTP.
```typescript
// products.component.ts
import { Component, signal, inject } from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
@Component({ selector: 'app-products', template: `` })
export class ProductsComponent {
private http = inject(HttpClient);
readonly category = signal('laptops');
// Angular 20 renamed the fields: request -> params and loader -> stream
readonly products = rxResource({
params: () => ({ category: this.category() }),
stream: ({ params }) =>
this.http.get(`/api/products?category=${params.category}`),
});
// products.value(), products.isLoading() and products.error() are all signals
}
```
Le renommage de `request`/`loader` dans Angular 19 vers `params`/`stream` dans Angular 20 est un piège fréquent, si bien que mentionner les noms de champs actuels signale une connaissance à jour lors d'un entretien. Quand le signal `category` change, `rxResource` annule la requête précédente et démarre un nouveau stream, offrant gratuitement la sémantique de `switchMap`. La [référence de l'API rxResource](https://v20.angular.dev/api/core/rxjs-interop/rxResource) documente l'ensemble du cycle de vie des statuts.
## Éviter les fuites mémoire avec takeUntilDestroyed
Tout `subscribe()` manuel qui survit à son composant fuit de la mémoire et peut déclencher des callbacks contre une vue détruite. Le correctif historique était le pattern `takeUntil(this.destroy$)` avec un Subject complété dans `ngOnDestroy`. Angular a remplacé ce boilerplate par `takeUntilDestroyed`, un operator qui lie l'abonnement au contexte d'injection courant ou à un `DestroyRef` explicite.
```typescript
// live-feed.component.ts
import { Component, inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { interval } from 'rxjs';
@Component({ selector: 'app-live-feed', template: `` })
export class LiveFeedComponent {
private destroyRef = inject(DestroyRef);
constructor() {
interval(1000)
// completes the subscription automatically when the component is destroyed
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((tick) => console.log('tick', tick));
}
}
```
Appelé dans un constructeur ou dans l'initialiseur d'un champ, `takeUntilDestroyed()` n'a besoin d'aucun argument car il lit le `DestroyRef` ambiant. Appelé ailleurs, par exemple à l'intérieur d'une méthode, il exige un `DestroyRef` explicite passé en argument, comme montré ci-dessus. Privilégier `toSignal` et `rxResource` évite entièrement les abonnements manuels, ce qui est la façon la plus propre de contourner les fuites ; quand un abonnement brut est réellement nécessaire, `takeUntilDestroyed` est le bon garde-fou.
> **Le piège du contexte d'injection**
>
> Appeler `takeUntilDestroyed()` sans argument en dehors d'un contexte d'injection lève une erreur d'exécution. S'abonner à l'intérieur d'un gestionnaire d'événement, d'un `setTimeout` ou d'un callback RxJS se situe hors de ce contexte, si bien que le `DestroyRef` doit être capturé plus tôt (généralement comme un champ injecté) et passé explicitement. Oublier cela est la cause la plus fréquente de l'erreur « takeUntilDestroyed() can only be used within an injection context ».
## Questions d'entretien Angular RxJS à répéter
Les recruteurs tournent souvent autour des mêmes sujets à fort signal. Savoir y répondre nettement sépare les candidats qui ont utilisé RxJS de ceux qui n'en ont fait que la lecture.
- **Observables chauds contre froids.** Un Observable froid démarre son producteur par abonnement, si bien que chaque abonné obtient une exécution indépendante (une nouvelle requête HTTP). Un Observable chaud partage un seul producteur entre les abonnés, ce que créent les Subjects et `share()`.
- **switchMap contre mergeMap contre concatMap contre exhaustMap.** `switchMap` annule l'Observable interne précédent (idéal pour la recherche). `mergeMap` exécute tous les Observables internes en parallèle. `concatMap` les met en file dans l'ordre. `exhaustMap` ignore les nouvelles entrées pendant qu'une est en cours (idéal pour empêcher les doubles soumissions de formulaire).
- **Comment prévenir les fuites mémoire.** Privilégier `toSignal`, `rxResource` ou le pipe `async`, qui se désabonnent tous automatiquement ; pour les abonnements manuels, utiliser `takeUntilDestroyed`.
- **Quand convertir un Observable en Signal.** Recourir à `toSignal` quand un template a besoin d'une valeur de manière synchrone et que la source est asynchrone, laissant la détection de changement lire la dernière valeur sans pipe `async`.
Travailler des versions notées de ces questions sous pression temporelle est le moyen le plus rapide de les intérioriser. Le [hub technologique Angular](/technologies/angular) regroupe les modules RxJS, Signals et détection de changement qui correspondent directement à ces questions.
## Conclusion
RxJS et les Signals sont des partenaires de la stack Angular moderne, pas des rivaux. Les enseignements à emporter dans le prochain projet ou entretien :
- Utiliser les Signals pour l'état synchrone du composant et les Observables pour les pipelines d'événements asynchrones qui ont besoin d'operators.
- Recourir à `switchMap` pour annuler le travail obsolète, à `exhaustMap` pour bloquer les doublons, et garder `catchError` sur l'Observable interne afin que le flux externe survive aux échecs.
- Exposer l'état d'un Subject via `asObservable()` et émettre des valeurs immuables pour garder la détection de changement prévisible.
- Relier les deux modèles avec `toSignal` et `toObservable`, et passer `initialValue` ou `requireSync` pour des types propres.
- Charger des données asynchrones de manière déclarative avec `rxResource` dans Angular 20, en se rappelant les noms de champs `params` et `stream`.
- Protéger chaque abonnement manuel restant avec `takeUntilDestroyed` pour éliminer les fuites.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/fr/blog/angular/rxjs-angular-operators-subjects-signals-interop