# RxJS w Angularze 2026: operatory, Subjecty i interop z Signals > RxJS w Angularze 2026: opanuj operatory, Subjecty i wzorce interoperacyjności z Signals używane na produkcji oraz pytania rekrutacyjne, które padają najczęściej. - Published: 2026-07-02 - Updated: 2026-07-07 - Author: SharpSkill - Tags: angular, rxjs, signals, observables, typescript, deep-dive - Reading time: 10 min --- RxJS w Angularze pozostaje fundamentem programowania asynchronicznego w 2026 roku, mimo że Signals zmieniają sposób, w jaki komponenty Angulara śledzą stan synchroniczny. Umiejętność rozpoznania, kiedy sięgnąć po Observable, kiedy lepiej sprawdzi się Signal i jak jedno współgra z drugim, stała się podstawową kompetencją każdego dewelopera Angulara. Ten szczegółowy przewodnik prowadzi przez operatory, Subjecty i wzorce interoperacyjności, które odróżniają pewną odpowiedź na rozmowie kwalifikacyjnej od takiej, która grzęźnie w miejscu. > **Signals kontra Observables w jednym zdaniu** > > Signals modelują stan synchroniczny, który zawsze ma bieżącą wartość; Observables modelują asynchroniczne strumienie zdarzeń rozłożone w czasie. Angular 20 zachowuje oba modele, a pakiet `@angular/core/rxjs-interop` jest oficjalnym mostem między nimi. ## Dlaczego RxJS wciąż ma znaczenie obok Angular Signals Wprowadzenie Signals skłoniło wiele zespołów do pytania, czy RxJS odchodzi do lamusa. Odpowiedź, trzy główne wersje później, brzmi: nie. Signals doskonale reprezentują stan, który ma wartość tu i teraz: licznik, pole formularza, wybraną zakładkę. Observables doskonale modelują zdarzenia, które napływają w czasie i mogą wymagać transformacji, anulowania lub łączenia: odpowiedzi HTTP, wiadomości WebSocket, wejście z klawiatury, zdarzenia routera i timery. Same API Angulara pokazują ten podział. `HttpClient` nadal zwraca Observables. Formularze reaktywne udostępniają `valueChanges` i `statusChanges` jako Observables. Zdarzenia routera płyną strumieniem przez Observable. Wszędzie tam, gdzie przez ten sam kanał przepływa wiele zdarzeń asynchronicznych, operatory RxJS pozostają najbardziej ekspresyjnym dostępnym narzędziem. Oficjalny [przewodnik po reaktywności Angulara](https://angular.dev/guide/signals/rxjs-interop) traktuje oba modele jako komplementarne, a nie konkurencyjne, a [dokumentacja RxJS](https://rxjs.dev/guide/overview) pozostaje obowiązkową lekturą przy każdym nietrywialnym przepływie danych. Praktyczna zasada, która broni się w code review: używaj Signals dla lokalnego stanu komponentu odczytywanego w szablonach, a Observables dla asynchronicznych potoków wymagających operatorów. Warstwa interoperacyjności łączy oba modele, więc żaden z nich nie staje się murem nie do przejścia. Operatory łączące potwierdzają ten wniosek. Koordynacja kilku źródeł asynchronicznych, jak scalanie parametrów trasy z zapisanymi filtrami użytkownika i strumieniem cen na żywo, to dokładnie to, do czego stworzono `combineLatest`, `withLatestFrom` i `forkJoin`. Signals potrafią wyprowadzać wartości obliczane z innych sygnałów, ale nie wyrażą koordynacji opartej na czasie w rodzaju „emituj dopiero, gdy wszystkie trzy źródła wyprodukują co najmniej jedną wartość". Ta deklaratywna kompozycja rozłożona w czasie to trwały powód, dla którego RxJS pozostaje w zestawie narzędzi. ## Kluczowe operatory RxJS, na których polega każdy deweloper Angulara Operatory to czyste funkcje, które przyjmują Observable i zwracają nowy. Klasycznym scenariuszem z rozmów kwalifikacyjnych jest wyszukiwanie w trakcie pisania, ponieważ uruchamia cztery operatory naraz: ogranicza częstotliwość wejścia, pomija duplikaty, anuluje nieaktualne żądania i odzyskuje sprawność po błędach. ```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 ) ) ); } ``` Subtelny szczegół, który badają rekruterzy, to dlaczego `switchMap` znajduje się na poziomie zewnętrznym, podczas gdy `catchError` jest zagnieżdżony wewnątrz niego. Umieszczenie `catchError` na wewnętrznym Observable HTTP sprawia, że nieudane żądanie zwraca pustą tablicę, a strumień zewnętrzny płynie dalej. Przeniesienie `catchError` do potoku zewnętrznego zakończyłoby całe wyszukiwanie po pierwszym błędzie, psując każde kolejne naciśnięcie klawisza. Cztery operatory spłaszczające odpowiadają, każdy na inne pytanie o współbieżność, a wybór niewłaściwego jest jednym z najczęstszych źródeł sytuacji wyścigu w aplikacjach Angulara. > **Wybór operatora spłaszczającego** > > Użyj `switchMap`, aby anulować poprzednie żądanie wewnętrzne i zachować tylko najnowsze (wyszukiwanie typu typeahead). Użyj `mergeMap`, aby uruchomić każde żądanie wewnętrzne współbieżnie (niezależne równoległe przesyłanie plików). Użyj `concatMap`, aby ustawić żądania w ścisłej kolejności (sekwencyjne zapisy, które nie mogą się nakładać). Użyj `exhaustMap`, aby ignorować nowe wejścia, dopóki bieżące się nie zakończy (blokowanie dwukrotnie klikniętego przycisku wysyłania). Właściwy wybór w tym miejscu zamienia narowistą funkcję w przewidywalną. [Moduł operatorów RxJS w SharpSkill](/technologies/angular/interview-questions/rxjs-operators) ćwiczy te rozróżnienia na ocenianych pytaniach, które odtwarzają dokładnie te sytuacje wyścigu, którym zapobiega każdy operator. ## Subjecty i multicasting: BehaviorSubject, ReplaySubject i Subject Zwykły Observable jest unicastowy: każdy subskrybent wyzwala nowe wykonanie. Subject jest jednocześnie Observable i Observerem, co czyni go multicastowym. Ta właściwość sprawia, że Subjecty są standardowym sposobem budowania lekkiego magazynu stanu lub szyny zdarzeń wewnątrz serwisu. Trzy warianty różnią się tym, co odtwarzają spóźnionym subskrybentom. Zwykły `Subject` nie emituje niczego z przeszłości, więc subskrybent widzi tylko wartości, które napłyną po jego subskrypcji. `BehaviorSubject` przechowuje bieżącą wartość i natychmiast ją odtwarza, dlatego wymaga wartości początkowej. `ReplaySubject` buforuje konfigurowalną liczbę wcześniejszych emisji i odtwarza je wszystkie. ```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 } } ``` Liczą się tu dwa nawyki. Po pierwsze, udostępnianie `asObservable()` zamiast samego Subjecta uniemożliwia zewnętrznemu kodowi wywołanie `next()` i modyfikowanie stanu tylnymi drzwiami. Po drugie, emitowanie nowej referencji tablicy zamiast modyfikacji w miejscu utrzymuje przewidywalność detekcji zmian i porównań równości. Ten wzorzec wyprzedza Signals i wciąż dobrze się sprawdza dla współdzielonego, wstrzykiwalnego stanu, z którego korzysta wiele niepowiązanych komponentów. Mimo to `BehaviorSubject` używany wyłącznie do przechowywania pojedynczej bieżącej wartości odczytywanej w szablonach często lepiej wyrazić dziś jako zapisywalny Signal, co eliminuje subskrypcję i ceremoniał `asObservable()`. Subject pozostaje mocniejszym wyborem, gdy magazyn musi też transformować, ograniczać częstotliwość lub łączyć swoje emisje z innymi strumieniami, ponieważ te operatory nie mają bezpośredniego odpowiednika wśród Signals. Rozstrzyganie między nimi zależnie od przypadku użycia, zamiast domyślnego stosowania jednego wszędzie, to oznaka biegłości, którą rekruterzy nagradzają. ## Interoperacyjność RxJS z Signals przy użyciu toSignal i toObservable Pakiet interoperacyjności dostarcza dwie sztandarowe funkcje. `toSignal` konwertuje Observable na Signal tylko do odczytu, który szablon może konsumować bezpośrednio, i automatycznie zarządza cyklem życia subskrypcji. `toObservable` robi odwrotnie, zamieniając Signal na Observable, aby operatory RxJS mogły przetwarzać jego zmiany. ```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 }); } ``` Opcja `initialValue` zasługuje na uwagę. Bez niej `toSignal` zwraca Signal typowany jako wartość lub `undefined`, ponieważ nic jeszcze nie wyemitowano. Przekazanie `initialValue: null` daje szablonowi zdefiniowany stan startowy i czystszy typ. Dla źródeł synchronicznych, takich jak `BehaviorSubject`, `requireSync: true` gwarantuje dostępność wartości przy pierwszym odczycie i całkowicie usuwa `undefined` z typu. Ten dwukierunkowy most pozwala bazie kodu przyjmować Signals stopniowo, bez przepisywania każdego istniejącego potoku Observable. Towarzyszący [moduł rozmów kwalifikacyjnych o Angular Signals](/technologies/angular/interview-questions/angular-signals) dogłębnie omawia model reaktywności, a [przegląd funkcji Signals w Angularze 18](/blog/angular/angular-18-signals-new-features) pokazuje, jak ewoluowały te prymitywy. ## Mostkowanie Observables do zasobów sygnałowych za pomocą rxResource Angular 20 promuje `rxResource` jako deklaratywny sposób ładowania danych asynchronicznych z Observable do stanu opartego na sygnałach. Śledzi stan ładowania, błędu i rozwiązania jako sygnały, co usuwa większość ręcznego księgowania subskrypcji, którego dawniej wymagały wywołania 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 } ``` Zmiana nazw z `request`/`loader` w Angularze 19 na `params`/`stream` w Angularze 20 często stanowi pułapkę, więc wspomnienie aktualnych nazw pól świadczy o aktualnej wiedzy podczas rozmowy. Gdy sygnał `category` się zmienia, `rxResource` anuluje poprzednie żądanie i uruchamia nowy strumień, dając semantykę `switchMap` za darmo. [Dokumentacja API rxResource](https://v20.angular.dev/api/core/rxjs-interop/rxResource) opisuje pełny cykl życia statusu. ## Unikanie wycieków pamięci dzięki takeUntilDestroyed Każde ręczne `subscribe()`, które przeżywa swój komponent, powoduje wyciek pamięci i może wywoływać callbacki na zniszczonym widoku. Historycznym rozwiązaniem był wzorzec `takeUntil(this.destroy$)` z Subjectem kończonym w `ngOnDestroy`. Angular zastąpił ten boilerplate operatorem `takeUntilDestroyed`, który wiąże subskrypcję z bieżącym kontekstem wstrzykiwania lub jawnym `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)); } } ``` Wywoływany wewnątrz konstruktora lub inicjalizatora pola, `takeUntilDestroyed()` nie potrzebuje argumentu, ponieważ odczytuje otaczający `DestroyRef`. Wywoływany gdziekolwiek indziej, na przykład wewnątrz metody, wymaga jawnie przekazanego `DestroyRef`, jak pokazano powyżej. Preferowanie `toSignal` i `rxResource` całkowicie eliminuje ręczne subskrypcje, co jest najczystszym sposobem uniknięcia wycieków; gdy surowa subskrypcja jest naprawdę potrzebna, `takeUntilDestroyed` jest właściwym zabezpieczeniem. > **Pułapka kontekstu wstrzykiwania** > > Wywołanie `takeUntilDestroyed()` bez argumentu poza kontekstem wstrzykiwania rzuca błąd wykonania. Subskrybowanie wewnątrz obsługi zdarzenia, `setTimeout` lub callbacku RxJS znajduje się poza tym kontekstem, więc `DestroyRef` trzeba przechwycić wcześniej (zwykle jako wstrzyknięte pole) i przekazać jawnie. Zapomnienie o tym jest najczęstszą przyczyną błędu „takeUntilDestroyed() can only be used within an injection context". ## Pytania rekrutacyjne o RxJS w Angularze warte przećwiczenia Rekruterzy zwykle krążą wokół tych samych tematów o wysokiej wartości sygnałowej. Umiejętność zwięzłej odpowiedzi na nie oddziela kandydatów, którzy używali RxJS, od tych, którzy jedynie o nim czytali. - **Hot kontra cold Observables.** Cold Observable uruchamia swojego producenta przy każdej subskrypcji, więc każdy subskrybent otrzymuje niezależne wykonanie (świeże żądanie HTTP). Hot Observable współdzieli jednego producenta między subskrybentami, co tworzą właśnie Subjecty i `share()`. - **switchMap kontra mergeMap kontra concatMap kontra exhaustMap.** `switchMap` anuluje poprzedni wewnętrzny Observable (idealny do wyszukiwania). `mergeMap` uruchamia wszystkie wewnętrzne Observables współbieżnie. `concatMap` ustawia je w kolejności. `exhaustMap` ignoruje nowe wejścia, gdy jedno jest w toku (idealny do zapobiegania podwójnemu wysyłaniu formularzy). - **Jak zapobiegać wyciekom pamięci.** Preferuj `toSignal`, `rxResource` lub pipe `async`, które wszystkie odsubskrybowują automatycznie; dla ręcznych subskrypcji używaj `takeUntilDestroyed`. - **Kiedy konwertować Observable na Signal.** Sięgnij po `toSignal`, gdy szablon potrzebuje wartości synchronicznie, a źródło jest asynchroniczne, pozwalając detekcji zmian odczytać najnowszą wartość bez pipe `async`. Przerabianie ocenianych wersji tych pytań pod presją czasu to najszybszy sposób na ich przyswojenie. [Centrum technologii Angular](/technologies/angular) grupuje moduły o RxJS, Signals i detekcji zmian, które bezpośrednio odpowiadają tym pytaniom. ## Podsumowanie RxJS i Signals są partnerami w nowoczesnym stosie Angulara, nie rywalami. Wnioski warte zabrania do następnego projektu lub na rozmowę: - Używaj Signals dla synchronicznego stanu komponentu i Observables dla asynchronicznych potoków zdarzeń wymagających operatorów. - Sięgaj po `switchMap`, aby anulować nieaktualną pracę, `exhaustMap`, aby blokować duplikaty, i trzymaj `catchError` na wewnętrznym Observable, aby strumień zewnętrzny przetrwał awarie. - Udostępniaj stan Subjecta przez `asObservable()` i emituj niemutowalne wartości, aby zachować przewidywalność detekcji zmian. - Łącz oba modele przez `toSignal` i `toObservable`, przekazując `initialValue` lub `requireSync` dla czystych typów. - Ładuj dane asynchroniczne deklaratywnie za pomocą `rxResource` w Angularze 20, pamiętając o nazwach pól `params` i `stream`. - Zabezpieczaj każdą pozostałą ręczną subskrypcję za pomocą `takeUntilDestroyed`, aby wyeliminować wycieki. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/angular/rxjs-angular-operators-subjects-signals-interop