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.

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 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 traktuje oba modele jako komplementarne, a nie konkurencyjne, a dokumentacja RxJS 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.
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
)
)
);
}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.
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 ć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.
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
}
}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.
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 dogłębnie omawia model reaktywności, a przegląd funkcji Signals w Angularze 18 pokazuje, jak ewoluowały te prymitywy.
Gotowy na rozmowy o Angular?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
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.
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
}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 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.
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.
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.
switchMapanuluje poprzedni wewnętrzny Observable (idealny do wyszukiwania).mergeMapuruchamia wszystkie wewnętrzne Observables współbieżnie.concatMapustawia je w kolejności.exhaustMapignoruje nowe wejścia, gdy jedno jest w toku (idealny do zapobiegania podwójnemu wysyłaniu formularzy). - Jak zapobiegać wyciekom pamięci. Preferuj
toSignal,rxResourcelub pipeasync, które wszystkie odsubskrybowują automatycznie; dla ręcznych subskrypcji używajtakeUntilDestroyed. - 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 pipeasync.
Przerabianie ocenianych wersji tych pytań pod presją czasu to najszybszy sposób na ich przyswojenie. Centrum technologii Angular grupuje moduły o RxJS, Signals i detekcji zmian, które bezpośrednio odpowiadają tym pytaniom.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
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 trzymajcatchErrorna 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
toSignalitoObservable, przekazującinitialValuelubrequireSyncdla czystych typów. - Ładuj dane asynchroniczne deklaratywnie za pomocą
rxResourcew Angularze 20, pamiętając o nazwach pólparamsistream. - Zabezpieczaj każdą pozostałą ręczną subskrypcję za pomocą
takeUntilDestroyed, aby wyeliminować wycieki.
Tagi
Udostępnij
Powiązane artykuły

NgRx Signal Store vs Klasyczny NgRx w 2026: Który Wybrać?
Kompleksowe porównanie NgRx Signal Store i klasycznego NgRx do zarządzania stanem w Angular. Praktyczne przykłady kodu i rekomendacje na rok 2026.

Angular @defer w 2026: deklaratywny lazy loading dla szybszych aplikacji
Opanuj bloki @defer w Angularze do deklaratywnego lazy loadingu. Dogłębne omówienie wyzwalaczy, prefetchingu, hydratacji przyrostowej, zachowania w SSR i praktycznych wzorców wydajności.

Angular 20 Resource API i httpResource: Kompletny przewodnik z pytaniami rekrutacyjnymi
Szczegolowy przewodnik po Resource API w Angular 20: resource(), rxResource() i httpResource() do reaktywnego pobierania danych. Walidacja Zod, sledzenie statusu ResourceStatus, migracja z HttpClient oraz pytania na rozmowy kwalifikacyjne Angular 2026.