# RxJS in Angular 2026: operators, Subjects en Signals-interop
> RxJS in Angular 2026: de operators, Subjects en Signals-interop-patronen die Angular-ontwikkelaars in productie gebruiken, plus de meest voorkomende sollicitatievragen.
- Published: 2026-07-02
- Updated: 2026-07-07
- Author: SharpSkill
- Tags: angular, rxjs, signals, observables, typescript, deep-dive
- Reading time: 10 min
---
RxJS blijft in Angular ook in 2026 de ruggengraat van asynchroon programmeren, ook al veranderen Signals hoe Angular-componenten synchrone state bijhouden. Weten wanneer een Observable de juiste keuze is, wanneer een Signal beter past en hoe beide samenwerken, hoort inmiddels tot de kernvaardigheden van elke Angular-ontwikkelaar. Deze deep dive loopt door de operators, Subjects en interop-patronen die een zelfverzekerd sollicitatieantwoord onderscheiden van een dat vastloopt.
> **Signals versus Observables in één zin**
>
> Signals modelleren synchrone state die altijd een huidige waarde heeft; Observables modelleren asynchrone stromen van gebeurtenissen in de tijd. Angular 20 behoudt beide, en het pakket `@angular/core/rxjs-interop` is de officiële brug ertussen.
## Waarom RxJS naast Angular Signals nog steeds telt
De introductie van Signals bracht veel teams ertoe zich af te vragen of RxJS op zijn retour was. Het antwoord luidt drie hoofdversies later: nee. Signals blinken uit in het weergeven van state die nu een waarde heeft: een teller, een formulierveld, een geselecteerde tab. Observables blinken uit in het modelleren van gebeurtenissen die in de tijd binnenkomen en transformatie, annulering of combinatie kunnen vereisen: HTTP-antwoorden, WebSocket-berichten, toetsenbordinvoer, router-gebeurtenissen en timers.
Angulars eigen API's maken deze scheiding zichtbaar. `HttpClient` levert nog altijd Observables. Reactive forms stellen `valueChanges` en `statusChanges` als Observables beschikbaar. Router-gebeurtenissen stromen door een Observable. Overal waar meerdere asynchrone gebeurtenissen door hetzelfde kanaal vloeien, blijven RxJS-operators het meest expressieve gereedschap dat er is. De officiële [Angular-gids over reactiviteit](https://angular.dev/guide/signals/rxjs-interop) behandelt beide modellen als aanvullend in plaats van concurrerend, en de [RxJS-documentatie](https://rxjs.dev/guide/overview) blijft een verplichte referentie voor elke niet-triviale datastroom.
De praktische regel die standhoudt in code review: Signals voor lokale componenten-state die in templates wordt gelezen, en Observables voor asynchrone pipelines die operators nodig hebben. De interop-laag verbindt beide, zodat geen van beide modellen een muur wordt.
Combinatie-operators onderstrepen dit punt. Meerdere asynchrone bronnen coördineren, zoals routeparameters samenvoegen met de opgeslagen filters van een gebruiker en een live prijsfeed, is precies waarvoor `combineLatest`, `withLatestFrom` en `forkJoin` zijn gebouwd. Signals kunnen berekende waarden afleiden uit andere signals, maar ze kunnen geen tijdgebonden coördinatie uitdrukken zoals "zend pas uit nadat alle drie de bronnen minstens één waarde hebben geproduceerd". Die declaratieve compositie in de tijd is de blijvende reden dat RxJS in de gereedschapskist blijft.
## Kern-RxJS-operators waarop elke Angular-ontwikkelaar vertrouwt
Operators zijn pure functies die een Observable aannemen en een nieuwe teruggeven. Het klassieke sollicitatiescenario is zoeken tijdens het typen, omdat het vier operators tegelijk in beweging zet: de invoer afremmen, duplicaten overslaan, verouderde verzoeken annuleren en herstellen van fouten.
```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
)
)
);
}
```
Het subtiele detail waar interviewers naar peilen, is waarom `switchMap` op het buitenste niveau zit terwijl `catchError` erin genest is. `catchError` op de binnenste HTTP-Observable plaatsen betekent dat een mislukt verzoek een lege array teruggeeft en de buitenste stroom blijft doorlopen. `catchError` naar de buitenste pipe verplaatsen zou de hele zoekopdracht na de eerste fout beëindigen en elke volgende toetsaanslag breken. De vier flattening-operators beantwoorden elk een andere vraag over gelijktijdigheid, en de verkeerde kiezen is een van de meest voorkomende oorzaken van race conditions in Angular-apps.
> **Een flattening-operator kiezen**
>
> Gebruik `switchMap` om het vorige binnenste verzoek te annuleren en alleen het nieuwste te behouden (typeahead-zoeken). Gebruik `mergeMap` om elk binnenste verzoek gelijktijdig uit te voeren (onafhankelijke parallelle uploads). Gebruik `concatMap` om verzoeken in strikte volgorde in de wachtrij te zetten (sequentiële schrijfacties die niet mogen overlappen). Gebruik `exhaustMap` om nieuwe invoer te negeren totdat de huidige klaar is (een dubbel aangeklikte verzendknop blokkeren).
Deze keuze goed maken is wat een wankele functie in een voorspelbare verandert. De [SharpSkill-module over RxJS-operators](/technologies/angular/interview-questions/rxjs-operators) oefent deze verschillen met beoordeelde vragen die precies de race conditions reproduceren die elke operator voorkomt.
## Subjects en multicasting: BehaviorSubject, ReplaySubject en Subject
Een gewone Observable is unicast: elke subscriber start een nieuwe uitvoering. Een Subject is tegelijk een Observable en een Observer, wat het multicast maakt. Die eigenschap maakt Subjects de standaardmanier om een lichtgewicht state-store of een event-bus binnen een service te bouwen.
De drie varianten verschillen in wat ze aan late subscribers herhalen. Een gewone `Subject` zendt niets uit het verleden uit, dus een subscriber ziet alleen waarden die binnenkomen nadat hij zich heeft geabonneerd. Een `BehaviorSubject` bewaart de huidige waarde en herhaalt die onmiddellijk, en daarom heeft het een beginwaarde nodig. Een `ReplaySubject` buffert een instelbaar aantal eerdere uitzendingen en herhaalt ze allemaal.
```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
}
}
```
Twee gewoonten zijn hier belangrijk. Ten eerste voorkomt het blootstellen van `asObservable()` in plaats van het Subject dat externe code `next()` aanroept en de state via de achterdeur muteert. Ten tweede houdt het uitzenden van een nieuwe array-referentie in plaats van in-place muteren de change detection en gelijkheidscontroles voorspelbaar. Dit patroon dateert van vóór Signals en werkt nog steeds goed voor gedeelde, injecteerbare state die meerdere onafhankelijke componenten gebruiken.
Dat gezegd hebbende, een `BehaviorSubject` dat puur wordt gebruikt om één enkele huidige waarde vast te houden die in templates wordt gelezen, laat zich tegenwoordig vaak beter uitdrukken als een beschrijfbaar Signal, wat de subscription en de `asObservable()`-ceremonie wegneemt. Het Subject blijft de sterkere keuze wanneer de store zijn uitzendingen ook moet transformeren, debouncen of combineren met andere streams, aangezien die operators geen direct Signal-equivalent hebben. Per situatie tussen beide kiezen, in plaats van overal op één terug te vallen, is een teken van vloeiendheid dat interviewers belonen.
## RxJS-Signals-interop met toSignal en toObservable
Het interop-pakket levert twee kernfuncties. `toSignal` zet een Observable om in een alleen-lezen Signal dat een template rechtstreeks kan gebruiken, en het beheert de levenscyclus van de subscription automatisch. `toObservable` doet het omgekeerde en verandert een Signal in een Observable, zodat RxJS-operators de wijzigingen ervan kunnen verwerken.
```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 });
}
```
De optie `initialValue` verdient aandacht. Zonder deze geeft `toSignal` een Signal terug dat is getypeerd als de waarde of `undefined`, omdat er nog niets is uitgezonden. `initialValue: null` doorgeven geeft het template een gedefinieerde beginstaat en een schoner type. Voor synchrone bronnen zoals een `BehaviorSubject` garandeert `requireSync: true` dat bij de eerste lezing een waarde beschikbaar is en verwijdert het `undefined` volledig uit het type. Deze tweerichtingsbrug is wat een codebase in staat stelt Signals stapsgewijs over te nemen zonder elke bestaande Observable-pipeline te herschrijven. De bijbehorende [Angular-Signals-sollicitatiemodule](/technologies/angular/interview-questions/angular-signals) behandelt het reactiviteitsmodel diepgaand, en het [overzicht van de Signals-features in Angular 18](/blog/angular/angular-18-signals-new-features) traceert hoe deze primitieven zijn geëvolueerd.
## Observables overbruggen naar signal resources met rxResource
Angular 20 promoot `rxResource` als de declaratieve manier om asynchrone data uit een Observable in signalgebaseerde state te laden. Het houdt laad-, fout- en opgeloste status bij als signals, wat het grootste deel van de handmatige subscription-boekhouding wegneemt die HTTP-aanroepen vroeger vereisten.
```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
}
```
De hernoeming van `request`/`loader` in Angular 19 naar `params`/`stream` in Angular 20 is een veelvoorkomende struikelblok, dus het noemen van de huidige veldnamen wijst in een sollicitatie op actuele kennis. Wanneer het `category`-signal verandert, annuleert `rxResource` het vorige verzoek en start het een nieuwe stream, wat de semantiek van `switchMap` gratis meelevert. De [rxResource-API-referentie](https://v20.angular.dev/api/core/rxjs-interop/rxResource) documenteert de volledige statuslevenscyclus.
## Geheugenlekken vermijden met takeUntilDestroyed
Elke handmatige `subscribe()` die zijn component overleeft, lekt geheugen en kan callbacks afvuren op een vernietigde view. De historische oplossing was een `takeUntil(this.destroy$)`-patroon met een Subject dat in `ngOnDestroy` werd afgesloten. Angular verving die boilerplate door `takeUntilDestroyed`, een operator die de subscription koppelt aan de huidige injection context of een expliciete `DestroyRef`.
```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));
}
}
```
Aangeroepen binnen een constructor of een veld-initialisator heeft `takeUntilDestroyed()` geen argument nodig, omdat het de omringende `DestroyRef` leest. Elders aangeroepen, zoals binnen een methode, vereist het een expliciet doorgegeven `DestroyRef`, zoals hierboven getoond. `toSignal` en `rxResource` verkiezen vermijdt handmatige subscriptions volledig, wat de schoonste manier is om lekken te ontwijken; wanneer een kale subscription echt nodig is, is `takeUntilDestroyed` de juiste bewaker.
> **De valkuil van de injection context**
>
> `takeUntilDestroyed()` zonder argument aanroepen buiten een injection context werpt een runtime-fout. Abonneren binnen een event handler, een `setTimeout` of een RxJS-callback valt buiten die context, dus de `DestroyRef` moet eerder worden vastgelegd (meestal als een geïnjecteerd veld) en expliciet worden doorgegeven. Dit vergeten is de meest voorkomende oorzaak van de fout "takeUntilDestroyed() can only be used within an injection context".
## Angular-RxJS-sollicitatievragen die het oefenen waard zijn
Interviewers cirkelen doorgaans rond dezelfde veelzeggende onderwerpen. Deze bondig kunnen beantwoorden scheidt kandidaten die RxJS hebben gebruikt van wie er alleen over heeft gelezen.
- **Hot versus cold Observables.** Een cold Observable start zijn producer per subscription, dus elke subscriber krijgt een onafhankelijke uitvoering (een nieuw HTTP-verzoek). Een hot Observable deelt één producer over subscribers heen, wat Subjects en `share()` creëren.
- **switchMap versus mergeMap versus concatMap versus exhaustMap.** `switchMap` annuleert het vorige binnenste Observable (ideaal voor zoeken). `mergeMap` voert alle binnenste Observables gelijktijdig uit. `concatMap` zet ze op volgorde in de wachtrij. `exhaustMap` negeert nieuwe invoer terwijl er één loopt (ideaal om dubbele formulierinzendingen te voorkomen).
- **Hoe geheugenlekken te voorkomen.** Verkies `toSignal`, `rxResource` of de `async`-pipe, die zich allemaal automatisch afmelden; gebruik voor handmatige subscriptions `takeUntilDestroyed`.
- **Wanneer een Observable naar een Signal converteren.** Grijp naar `toSignal` wanneer een template synchroon een waarde nodig heeft en de bron asynchroon is, zodat de change detection de nieuwste waarde kan lezen zonder een `async`-pipe.
Beoordeelde versies hiervan onder tijdsdruk doorwerken is de snelste manier om ze eigen te maken. De [Angular-technologiehub](/technologies/angular) groepeert de modules over RxJS, Signals en change detection die direct op deze vragen aansluiten.
## Conclusie
RxJS en Signals zijn partners in de moderne Angular-stack, geen rivalen. De inzichten die het waard zijn om mee te nemen naar het volgende project of sollicitatiegesprek:
- Gebruik Signals voor synchrone componenten-state en Observables voor asynchrone gebeurtenis-pipelines die operators nodig hebben.
- Grijp naar `switchMap` om verouderd werk te annuleren, naar `exhaustMap` om duplicaten te blokkeren, en houd `catchError` op het binnenste Observable zodat de buitenste stroom fouten overleeft.
- Stel Subject-state bloot via `asObservable()` en zend onveranderlijke waarden uit om de change detection voorspelbaar te houden.
- Overbrug de twee modellen met `toSignal` en `toObservable`, en geef `initialValue` of `requireSync` door voor schone types.
- Laad asynchrone data declaratief met `rxResource` in Angular 20, met de veldnamen `params` en `stream` in gedachten.
- Bescherm elke resterende handmatige subscription met `takeUntilDestroyed` om lekken te elimineren.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/nl/blog/angular/rxjs-angular-operators-subjects-signals-interop