# RxJS in Angular 2026: operatori, Subjects e interop con i Signals
> RxJS in Angular 2026: operatori, Subjects e i pattern di interop con i Signals che gli sviluppatori Angular usano in produzione, più le domande da colloquio più frequenti.
- Published: 2026-07-02
- Updated: 2026-07-07
- Author: SharpSkill
- Tags: angular, rxjs, signals, observables, typescript, deep-dive
- Reading time: 10 min
---
RxJS in Angular resta la spina dorsale della programmazione asincrona nel 2026, anche mentre i Signals ridisegnano il modo in cui i componenti Angular tracciano lo stato sincrono. Sapere quando ricorrere a un Observable, quando un Signal si adatta meglio e come i due interoperano è ormai una competenza fondamentale per qualsiasi sviluppatore Angular. Questo approfondimento attraversa gli operatori, i Subjects e i pattern di interop che distinguono una risposta sicura in un colloquio da una che si blocca.
> **Signals vs Observables in una riga**
>
> I Signals modellano lo stato sincrono che ha sempre un valore corrente; gli Observables modellano flussi asincroni di eventi nel tempo. Angular 20 mantiene entrambi, e il pacchetto `@angular/core/rxjs-interop` è il ponte ufficiale tra i due.
## Perché RxJS conta ancora accanto ai Signals di Angular
L'introduzione dei Signals ha spinto molti team a chiedersi se RxJS fosse in via di dismissione. La risposta, tre versioni maggiori dopo, è no. I Signals eccellono nel rappresentare lo stato che ha un valore in questo momento: un contatore, un campo di un form, una scheda selezionata. Gli Observables eccellono nel modellare eventi che arrivano nel tempo e possono richiedere trasformazione, cancellazione o combinazione: risposte HTTP, messaggi WebSocket, input da tastiera, eventi del router e timer.
Le stesse API di Angular rendono visibile questa divisione. `HttpClient` restituisce ancora Observables. I reactive form espongono `valueChanges` e `statusChanges` come Observables. Gli eventi del router scorrono attraverso un Observable. Ovunque più eventi asincroni fluiscano nello stesso canale, gli operatori RxJS restano lo strumento più espressivo a disposizione. La [guida ufficiale alla reattività di Angular](https://angular.dev/guide/signals/rxjs-interop) tratta i due modelli come complementari anziché in competizione, e la [documentazione di RxJS](https://rxjs.dev/guide/overview) rimane un riferimento obbligato per qualsiasi flusso di dati non banale.
La regola pratica che regge in code review: Signals per lo stato locale del componente letto nei template, e Observables per le pipeline asincrone che richiedono operatori. Lo strato di interop collega i due, così nessun modello diventa un muro.
Gli operatori di combinazione rafforzano questo punto. Coordinare più sorgenti asincrone, come fondere i parametri di rotta con i filtri salvati di un utente e un feed di prezzi in tempo reale, è esattamente ciò per cui `combineLatest`, `withLatestFrom` e `forkJoin` sono stati costruiti. I Signals possono derivare valori calcolati da altri signals, ma non possono esprimere una coordinazione basata sul tempo come "emetti solo dopo che tutte e tre le sorgenti hanno prodotto almeno un valore". Quella composizione dichiarativa nel tempo è la ragione duratura per cui RxJS resta nella cassetta degli attrezzi.
## Operatori RxJS fondamentali su cui ogni sviluppatore Angular fa affidamento
Gli operatori sono funzioni pure che prendono un Observable e ne restituiscono uno nuovo. Lo scenario classico da colloquio è la ricerca durante la digitazione, perché mette all'opera quattro operatori contemporaneamente: limitare l'input, saltare i duplicati, cancellare le richieste obsolete e recuperare dagli errori.
```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
)
)
);
}
```
Il dettaglio sottile che gli intervistatori sondano è perché `switchMap` si trova al livello esterno mentre `catchError` è annidato al suo interno. Collocare `catchError` sull'Observable HTTP interno significa che una richiesta fallita restituisce un array vuoto e il flusso esterno continua a scorrere. Spostare `catchError` sulla pipe esterna terminerebbe l'intera ricerca dopo il primo errore, rompendo ogni pressione di tasto successiva. I quattro operatori di flattening rispondono ciascuno a una diversa domanda di concorrenza, e scegliere quello sbagliato è una delle fonti più comuni di race condition nelle app Angular.
> **Scegliere un operatore di flattening**
>
> Usa `switchMap` per cancellare la richiesta interna precedente e mantenere solo l'ultima (ricerca typeahead). Usa `mergeMap` per eseguire ogni richiesta interna in concorrenza (upload paralleli indipendenti). Usa `concatMap` per accodare le richieste in ordine rigoroso (scritture sequenziali che non devono sovrapporsi). Usa `exhaustMap` per ignorare i nuovi input finché quello corrente non termina (bloccare un pulsante di invio cliccato due volte).
Azzeccare questa scelta è ciò che trasforma una funzionalità instabile in una prevedibile. Il [modulo SharpSkill sugli operatori RxJS](/technologies/angular/interview-questions/rxjs-operators) allena queste distinzioni con domande a punteggio che riproducono le esatte race condition che ciascun operatore previene.
## Subjects e multicasting: BehaviorSubject, ReplaySubject e Subject
Un Observable semplice è unicast: ogni subscriber innesca un'esecuzione nuova. Un Subject è al contempo un Observable e un Observer, il che lo rende multicast. Questa proprietà fa dei Subjects il modo standard per costruire uno store di stato leggero o un bus di eventi all'interno di un service.
Le tre varianti differiscono per ciò che riproducono ai subscriber tardivi. Un `Subject` semplice non emette nulla dal passato, quindi un subscriber vede solo i valori che arrivano dopo che si è iscritto. Un `BehaviorSubject` memorizza il valore corrente e lo riproduce immediatamente, ed è per questo che richiede un valore iniziale. Un `ReplaySubject` bufferizza un numero configurabile di emissioni passate e le riproduce tutte.
```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
}
}
```
Qui contano due abitudini. Primo, esporre `asObservable()` anziché il Subject impedisce al codice esterno di chiamare `next()` e mutare lo stato dalla porta di servizio. Secondo, emettere un nuovo riferimento di array invece di mutare in loco mantiene prevedibili la change detection e i controlli di uguaglianza. Questo pattern precede i Signals e funziona ancora bene per lo stato condiviso e iniettabile che più componenti tra loro slegati consumano.
Detto ciò, un `BehaviorSubject` usato puramente per tenere un singolo valore corrente letto nei template oggi si esprime spesso meglio come Signal scrivibile, che rimuove la sottoscrizione e la cerimonia di `asObservable()`. Il Subject resta la scelta più forte quando lo store deve anche trasformare, debounce o combinare le sue emissioni con altri flussi, dato che quegli operatori non hanno equivalente diretto tra i Signals. Decidere tra i due caso per caso, anziché ripiegare ovunque su uno, è un segno di padronanza che gli intervistatori premiano.
## Interop RxJS-Signals con toSignal e toObservable
Il pacchetto di interop offre due funzioni di punta. `toSignal` converte un Observable in un Signal di sola lettura che un template può consumare direttamente, e gestisce automaticamente il ciclo di vita della sottoscrizione. `toObservable` fa l'inverso, trasformando un Signal in un Observable così che gli operatori RxJS possano elaborarne i cambiamenti.
```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'opzione `initialValue` merita attenzione. Senza di essa, `toSignal` restituisce un Signal tipizzato come il valore oppure `undefined`, perché nulla ha ancora emesso. Passare `initialValue: null` dà al template uno stato iniziale definito e un tipo più pulito. Per sorgenti sincrone come un `BehaviorSubject`, `requireSync: true` garantisce che un valore sia disponibile alla prima lettura ed elimina del tutto `undefined` dal tipo. Questo ponte bidirezionale è ciò che permette a una codebase di adottare i Signals in modo incrementale senza riscrivere ogni pipeline Observable esistente. Il [modulo di colloquio sui Signals di Angular](/technologies/angular/interview-questions/angular-signals) copre in profondità il modello di reattività, e la [panoramica delle funzionalità dei Signals in Angular 18](/blog/angular/angular-18-signals-new-features) ricostruisce come queste primitive si sono evolute.
## Collegare gli Observables alle signal resource con rxResource
Angular 20 promuove `rxResource` come il modo dichiarativo per caricare dati asincroni da un Observable in uno stato basato su signal. Traccia lo stato di caricamento, errore e risoluzione come signals, il che rimuove gran parte della contabilità manuale delle sottoscrizioni che le chiamate HTTP richiedevano un tempo.
```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
}
```
La rinomina da `request`/`loader` in Angular 19 a `params`/`stream` in Angular 20 è un inciampo frequente, quindi menzionare i nomi attuali dei campi segnala conoscenze aggiornate in un colloquio. Quando il signal `category` cambia, `rxResource` cancella la richiesta precedente e avvia un nuovo stream, offrendo gratuitamente la semantica di `switchMap`. Il [riferimento all'API rxResource](https://v20.angular.dev/api/core/rxjs-interop/rxResource) documenta l'intero ciclo di vita dello stato.
## Evitare i memory leak con takeUntilDestroyed
Qualsiasi `subscribe()` manuale che sopravvive al proprio componente causa un memory leak e può far scattare callback contro una view distrutta. La correzione storica era il pattern `takeUntil(this.destroy$)` con un Subject completato in `ngOnDestroy`. Angular ha sostituito quel boilerplate con `takeUntilDestroyed`, un operatore che lega la sottoscrizione all'injection context corrente o a un `DestroyRef` esplicito.
```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));
}
}
```
Chiamato dentro un costruttore o un inizializzatore di campo, `takeUntilDestroyed()` non necessita di argomenti perché legge il `DestroyRef` ambientale. Chiamato altrove, come dentro un metodo, richiede un `DestroyRef` esplicito passato in ingresso, come mostrato sopra. Preferire `toSignal` e `rxResource` evita del tutto le sottoscrizioni manuali, che è il modo più pulito per aggirare i leak; quando serve davvero una sottoscrizione grezza, `takeUntilDestroyed` è la guardia corretta.
> **Il tranello dell'injection context**
>
> Chiamare `takeUntilDestroyed()` senza argomenti fuori da un injection context lancia un errore a runtime. Sottoscrivere dentro un event handler, un `setTimeout` o una callback RxJS è fuori da quel context, quindi il `DestroyRef` va catturato prima (tipicamente come campo iniettato) e passato esplicitamente. Dimenticarlo è la causa più frequente dell'errore "takeUntilDestroyed() can only be used within an injection context".
## Domande da colloquio su Angular e RxJS che vale la pena ripassare
Gli intervistatori tendono a girare attorno agli stessi argomenti ad alto segnale. Saper rispondere a questi in modo netto separa i candidati che hanno usato RxJS da quelli che ne hanno solo letto.
- **Observables hot contro cold.** Un Observable cold avvia il suo producer per ogni sottoscrizione, quindi ogni subscriber ottiene un'esecuzione indipendente (una richiesta HTTP nuova). Un Observable hot condivide un solo producer tra i subscriber, ed è ciò che creano i Subjects e `share()`.
- **switchMap contro mergeMap contro concatMap contro exhaustMap.** `switchMap` cancella l'Observable interno precedente (ideale per la ricerca). `mergeMap` esegue tutti gli Observable interni in concorrenza. `concatMap` li accoda in ordine. `exhaustMap` ignora i nuovi input mentre uno è in esecuzione (ideale per prevenire i doppi invii di form).
- **Come prevenire i memory leak.** Preferire `toSignal`, `rxResource` o la pipe `async`, che si disiscrivono tutte automaticamente; per le sottoscrizioni manuali, usare `takeUntilDestroyed`.
- **Quando convertire un Observable in un Signal.** Ricorrere a `toSignal` quando un template ha bisogno di un valore in modo sincrono e la sorgente è asincrona, lasciando che la change detection legga l'ultimo valore senza una pipe `async`.
Lavorare su versioni a punteggio di queste sotto pressione di tempo è il modo più rapido per interiorizzarle. L'[hub tecnologico Angular](/technologies/angular) raggruppa i moduli su RxJS, Signals e change detection che mappano direttamente su queste domande.
## Conclusione
RxJS e Signals sono partner nello stack Angular moderno, non rivali. Le conclusioni che vale la pena portarsi nel prossimo progetto o colloquio:
- Usare i Signals per lo stato sincrono del componente e gli Observables per le pipeline di eventi asincroni che richiedono operatori.
- Ricorrere a `switchMap` per cancellare il lavoro obsoleto, a `exhaustMap` per bloccare i duplicati, e tenere `catchError` sull'Observable interno così che il flusso esterno sopravviva ai fallimenti.
- Esporre lo stato dei Subject tramite `asObservable()` ed emettere valori immutabili per mantenere prevedibile la change detection.
- Collegare i due modelli con `toSignal` e `toObservable`, e passare `initialValue` o `requireSync` per tipi puliti.
- Caricare dati asincroni in modo dichiarativo con `rxResource` in Angular 20, ricordando i nomi dei campi `params` e `stream`.
- Proteggere ogni sottoscrizione manuale residua con `takeUntilDestroyed` per eliminare i leak.
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/it/blog/angular/rxjs-angular-operators-subjects-signals-interop