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.

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.
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 traite les deux modèles comme complémentaires plutôt que concurrents, et la documentation RxJS 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.
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: `<input [formControl]="query" placeholder="Search repositories" />`,
})
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.
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 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.
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<string[]>([]);
// 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.
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 couvre le modèle de réactivité en profondeur, et l'aperçu des fonctionnalités Signals d'Angular 18 retrace l'évolution de ces primitives.
Prêt à réussir tes entretiens Angular ?
Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.
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.
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<Product[]>(`/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 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.
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.
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.
switchMapannule l'Observable interne précédent (idéal pour la recherche).mergeMapexécute tous les Observables internes en parallèle.concatMaples met en file dans l'ordre.exhaustMapignore 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,rxResourceou le pipeasync, qui se désabonnent tous automatiquement ; pour les abonnements manuels, utilisertakeUntilDestroyed. - Quand convertir un Observable en Signal. Recourir à
toSignalquand 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 pipeasync.
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 regroupe les modules RxJS, Signals et détection de changement qui correspondent directement à ces questions.
Passe à la pratique !
Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.
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 à
switchMappour annuler le travail obsolète, àexhaustMappour bloquer les doublons, et gardercatchErrorsur 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
toSignalettoObservable, et passerinitialValueourequireSyncpour des types propres. - Charger des données asynchrones de manière déclarative avec
rxResourcedans Angular 20, en se rappelant les noms de champsparamsetstream. - Protéger chaque abonnement manuel restant avec
takeUntilDestroyedpour éliminer les fuites.
Tags
Partager
Articles similaires

Angular 20 en 2026 : Resource API, httpResource et questions d'entretien
Angular 20 introduit httpResource et stabilise la Resource API pour la récupération de données fondée sur les signals. Un tutoriel pratique sur resource(), rxResource(), httpResource(), la validation Zod et les questions d'entretien courantes.

Angular 19 en entretien : Signals, SSR et les questions incontournables
Les questions d'entretien Angular 19 les plus fréquentes : Signals, SSR avec hydratation incrémentale, détection de changement zoneless et nouvelles API réactives.

Angular 19 Zoneless : Detection de Changements sans Zone.js et Gains de Performance
Guide complet sur la detection de changements sans Zone.js dans Angular 19 et 20. Configuration de provideZonelessChangeDetection, migration vers les signals, SSR sans Zone.js, benchmarks de performance et pieges a eviter lors de la migration.