# RxJS no Angular 2026: operators, Subjects e interop com Signals > RxJS no Angular 2026: domine os operators, os Subjects e os padrões de interop com Signals usados em produção, além das perguntas de entrevista mais frequentes. - Published: 2026-07-02 - Updated: 2026-07-07 - Author: SharpSkill - Tags: angular, rxjs, signals, observables, typescript, deep-dive - Reading time: 10 min --- RxJS continua sendo a espinha dorsal da programação assíncrona no Angular em 2026, mesmo com os Signals remodelando a forma como os componentes Angular rastreiam o estado síncrono. Saber quando recorrer a um Observable, quando um Signal se encaixa melhor e como os dois interoperam é hoje uma habilidade central para qualquer desenvolvedor Angular. Esta análise aprofundada percorre os operators, os Subjects e os padrões de interop que separam uma resposta segura de entrevista de uma resposta travada. > **Signals contra Observables em uma linha** > > Os Signals modelam estado síncrono que sempre possui um valor atual; os Observables modelam fluxos assíncronos de eventos ao longo do tempo. O Angular 20 mantém os dois, e o pacote `@angular/core/rxjs-interop` é a ponte oficial entre eles. ## Por que RxJS ainda importa ao lado dos Angular Signals A chegada dos Signals levou muitas equipes a perguntar se o RxJS estava de saída. A resposta, três versões maiores depois, é não. Os Signals se destacam ao representar estado que tem um valor neste momento: um contador, um campo de formulário, uma aba selecionada. Os Observables se destacam ao modelar eventos que chegam ao longo do tempo e que podem precisar de transformação, cancelamento ou combinação: respostas HTTP, mensagens WebSocket, entrada de teclado, eventos do router e temporizadores. As próprias APIs do Angular tornam essa divisão visível. `HttpClient` ainda retorna Observables. Os formulários reativos expõem `valueChanges` e `statusChanges` como Observables. Os eventos do router fluem através de um Observable. Onde quer que múltiplos eventos assíncronos trafeguem pelo mesmo canal, os operators do RxJS continuam sendo a ferramenta mais expressiva disponível. O [guia oficial de reatividade do Angular](https://angular.dev/guide/signals/rxjs-interop) trata os dois modelos como complementares em vez de concorrentes, e a [documentação do RxJS](https://rxjs.dev/guide/overview) segue sendo referência obrigatória para qualquer fluxo de dados não trivial. A regra prática que se sustenta na revisão de código: usar Signals para o estado local do componente lido nos templates, e usar Observables para os pipelines assíncronos que precisam de operators. A camada de interop conecta os dois para que nenhum modelo se torne uma parede. Os operators de combinação reforçam o ponto. Coordenar várias fontes assíncronas, como mesclar os parâmetros de rota com os filtros salvos de um usuário e um feed de preços ao vivo, é exatamente para o que `combineLatest`, `withLatestFrom` e `forkJoin` foram criados. Os Signals conseguem derivar valores calculados a partir de outros Signals, mas não conseguem expressar coordenação baseada em tempo como "emitir apenas depois que as três fontes tenham produzido ao menos um valor". Essa composição declarativa no tempo é a razão duradoura pela qual o RxJS permanece na caixa de ferramentas. ## Os operators do RxJS de que todo desenvolvedor Angular depende Os operators são funções puras que recebem um Observable e retornam um novo. O cenário clássico de entrevista é a busca conforme se digita, porque exercita quatro operators de uma vez: limitar a entrada, ignorar duplicatas, cancelar requisições obsoletas e recuperar-se de erros. ```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 ) ) ); } ``` O detalhe sutil que os entrevistadores sondam é por que `switchMap` fica no nível externo enquanto `catchError` está aninhado dentro dele. Colocar `catchError` no Observable HTTP interno significa que uma requisição que falha retorna um array vazio e o fluxo externo segue circulando. Mover `catchError` para o pipe externo encerraria toda a busca após o primeiro erro, quebrando cada tecla digitada em seguida. Os quatro operators de achatamento respondem cada um a uma pergunta de concorrência diferente, e escolher o errado é uma das fontes mais comuns de race conditions em aplicações Angular. > **Como escolher um operator de achatamento** > > Usar `switchMap` para cancelar a requisição interna anterior e manter apenas a mais recente (busca typeahead). Usar `mergeMap` para executar cada requisição interna em paralelo (uploads independentes). Usar `concatMap` para enfileirar as requisições em ordem estrita (escritas sequenciais que não devem se sobrepor). Usar `exhaustMap` para ignorar novas entradas até que a atual termine (bloquear um botão de envio clicado duas vezes). Acertar nessa escolha é o que transforma uma funcionalidade instável em uma previsível. O [módulo de operators do RxJS da SharpSkill](/technologies/angular/interview-questions/rxjs-operators) treina essas distinções com questões avaliadas que reproduzem as race conditions exatas que cada operator previne. ## Subjects e multicasting: BehaviorSubject, ReplaySubject e Subject Um Observable simples é unicast: cada assinante dispara uma execução nova. Um Subject é ao mesmo tempo um Observable e um Observer, o que o torna multicast. Essa propriedade faz dos Subjects a maneira padrão de construir um store de estado leve ou um barramento de eventos dentro de um serviço. As três variantes diferem no que reproduzem para assinantes tardios. Um `Subject` simples não emite nada do passado, então um assinante só vê os valores que chegam após se inscrever. Um `BehaviorSubject` armazena o valor atual e o reproduz de imediato, razão pela qual precisa de um valor inicial. Um `ReplaySubject` mantém em buffer um número configurável de emissões passadas e reproduz todas elas. ```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 } } ``` Dois hábitos importam aqui. Primeiro, expor `asObservable()` em vez do Subject impede que o código externo chame `next()` e mute o estado pela porta dos fundos. Segundo, emitir uma nova referência de array em vez de mutar no lugar mantém previsíveis a detecção de mudanças e as verificações de igualdade. Esse padrão antecede os Signals e ainda funciona bem para estado compartilhado e injetável que vários componentes sem relação consomem. Dito isso, um `BehaviorSubject` usado apenas para segurar um único valor atual lido nos templates hoje é muitas vezes melhor expresso como um Signal gravável, que remove a assinatura e a cerimônia do `asObservable()`. O Subject continua sendo a escolha mais forte quando o store também precisa transformar, aplicar debounce ou combinar suas emissões com outros fluxos, já que esses operators não têm equivalente direto do lado dos Signals. Decidir entre os dois caso a caso, em vez de impor um por padrão em toda parte, é uma marca de fluência que os entrevistadores recompensam. ## Interop de RxJS com Signals usando toSignal e toObservable O pacote de interop entrega duas funções principais. `toSignal` converte um Observable em um Signal somente leitura que um template pode consumir diretamente, e gerencia o ciclo de vida da assinatura de forma automática. `toObservable` faz o inverso, transformando um Signal em um Observable para que os operators do RxJS possam processar suas mudanças. ```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 }); } ``` A opção `initialValue` merece atenção. Sem ela, `toSignal` retorna um Signal tipado como o valor ou `undefined`, porque nada emitiu ainda. Passar `initialValue: null` dá ao template um estado inicial definido e um tipo mais limpo. Para fontes síncronas como um `BehaviorSubject`, `requireSync: true` garante que um valor esteja disponível na primeira leitura e remove `undefined` do tipo por completo. Essa ponte bidirecional é o que permite a uma base de código adotar os Signals de forma incremental sem reescrever cada pipeline Observable existente. O [módulo de entrevista de Angular Signals](/technologies/angular/interview-questions/angular-signals) cobre o modelo de reatividade em profundidade, e o [resumo de recursos de Signals do Angular 18](/blog/angular/angular-18-signals-new-features) traça como essas primitivas evoluíram. ## Conectando Observables a recursos signal com rxResource O Angular 20 promove `rxResource` como a forma declarativa de carregar dados assíncronos de um Observable para estado baseado em signals. Ele rastreia os estados de carregamento, erro e resolução como signals, o que elimina a maior parte do controle manual de assinatura que as chamadas HTTP costumavam exigir. ```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 } ``` A renomeação de `request`/`loader` no Angular 19 para `params`/`stream` no Angular 20 é um tropeço frequente, então mencionar os nomes de campo atuais sinaliza conhecimento atualizado em uma entrevista. Quando o signal `category` muda, `rxResource` cancela a requisição anterior e inicia um novo stream, entregando a semântica de `switchMap` de graça. A [referência da API rxResource](https://v20.angular.dev/api/core/rxjs-interop/rxResource) documenta todo o ciclo de vida dos estados. ## Evitando vazamentos de memória com takeUntilDestroyed Qualquer `subscribe()` manual que sobreviva ao seu componente vaza memória e pode disparar callbacks contra uma view destruída. A correção histórica era o padrão `takeUntil(this.destroy$)` com um Subject completado em `ngOnDestroy`. O Angular substituiu esse boilerplate por `takeUntilDestroyed`, um operator que amarra a assinatura ao contexto de injeção atual ou a um `DestroyRef` explícito. ```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)); } } ``` Chamado dentro de um construtor ou de um inicializador de campo, `takeUntilDestroyed()` não precisa de argumento porque lê o `DestroyRef` do ambiente. Chamado em qualquer outro lugar, como dentro de um método, ele exige um `DestroyRef` explícito passado como argumento, conforme mostrado acima. Preferir `toSignal` e `rxResource` evita as assinaturas manuais por completo, que é a maneira mais limpa de contornar os vazamentos; quando uma assinatura crua é de fato necessária, `takeUntilDestroyed` é a proteção correta. > **A armadilha do contexto de injeção** > > Chamar `takeUntilDestroyed()` sem argumento fora de um contexto de injeção lança um erro em tempo de execução. Inscrever-se dentro de um manipulador de evento, de um `setTimeout` ou de um callback do RxJS fica fora desse contexto, então o `DestroyRef` precisa ser capturado antes (normalmente como um campo injetado) e passado explicitamente. Esquecer disso é a causa mais frequente do erro "takeUntilDestroyed() can only be used within an injection context". ## Perguntas de entrevista de Angular RxJS que vale a pena ensaiar Os entrevistadores costumam circular pelos mesmos tópicos de alto sinal. Saber respondê-los com clareza separa os candidatos que já usaram RxJS daqueles que apenas leram a respeito. - **Observables quentes contra frios.** Um Observable frio inicia seu produtor por assinatura, então cada assinante obtém uma execução independente (uma nova requisição HTTP). Um Observable quente compartilha um único produtor entre os assinantes, que é o que os Subjects e `share()` criam. - **switchMap contra mergeMap contra concatMap contra exhaustMap.** `switchMap` cancela o Observable interno anterior (ideal para busca). `mergeMap` executa todos os Observables internos em paralelo. `concatMap` os enfileira em ordem. `exhaustMap` ignora novas entradas enquanto uma está em curso (ideal para prevenir envios duplos de formulário). - **Como prevenir vazamentos de memória.** Preferir `toSignal`, `rxResource` ou o pipe `async`, que se desinscrevem todos de forma automática; para assinaturas manuais, usar `takeUntilDestroyed`. - **Quando converter um Observable em um Signal.** Recorrer a `toSignal` quando um template precisa de um valor de forma síncrona e a fonte é assíncrona, deixando a detecção de mudanças ler o valor mais recente sem um pipe `async`. Trabalhar versões avaliadas dessas perguntas sob pressão de tempo é o caminho mais rápido para interiorizá-las. O [hub de tecnologia Angular](/technologies/angular) agrupa os módulos de RxJS, Signals e detecção de mudanças que se mapeiam diretamente para essas perguntas. ## Conclusão RxJS e Signals são parceiros no stack moderno do Angular, não rivais. As conclusões que vale a pena levar para o próximo projeto ou entrevista: - Usar Signals para o estado síncrono do componente e Observables para os pipelines de eventos assíncronos que precisam de operators. - Recorrer a `switchMap` para cancelar trabalho obsoleto, a `exhaustMap` para bloquear duplicatas, e manter `catchError` no Observable interno para que o fluxo externo sobreviva às falhas. - Expor o estado de um Subject por meio de `asObservable()` e emitir valores imutáveis para manter previsível a detecção de mudanças. - Unir os dois modelos com `toSignal` e `toObservable`, e passar `initialValue` ou `requireSync` para tipos limpos. - Carregar dados assíncronos de forma declarativa com `rxResource` no Angular 20, lembrando dos nomes de campo `params` e `stream`. - Proteger cada assinatura manual restante com `takeUntilDestroyed` para eliminar os vazamentos. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/angular/rxjs-angular-operators-subjects-signals-interop