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.

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.
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 trata os dois modelos como complementares em vez de concorrentes, e a documentação do RxJS 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.
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
)
)
);
}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.
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 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.
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
}
}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.
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 cobre o modelo de reatividade em profundidade, e o resumo de recursos de Signals do Angular 18 traça como essas primitivas evoluíram.
Pronto para mandar bem nas entrevistas de Angular?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
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.
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
}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 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.
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.
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.
switchMapcancela o Observable interno anterior (ideal para busca).mergeMapexecuta todos os Observables internos em paralelo.concatMapos enfileira em ordem.exhaustMapignora novas entradas enquanto uma está em curso (ideal para prevenir envios duplos de formulário). - Como prevenir vazamentos de memória. Preferir
toSignal,rxResourceou o pipeasync, que se desinscrevem todos de forma automática; para assinaturas manuais, usartakeUntilDestroyed. - Quando converter um Observable em um Signal. Recorrer a
toSignalquando 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 pipeasync.
Trabalhar versões avaliadas dessas perguntas sob pressão de tempo é o caminho mais rápido para interiorizá-las. O hub de tecnologia Angular agrupa os módulos de RxJS, Signals e detecção de mudanças que se mapeiam diretamente para essas perguntas.
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
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
switchMappara cancelar trabalho obsoleto, aexhaustMappara bloquear duplicatas, e mantercatchErrorno 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
toSignaletoObservable, e passarinitialValueourequireSyncpara tipos limpos. - Carregar dados assíncronos de forma declarativa com
rxResourceno Angular 20, lembrando dos nomes de campoparamsestream. - Proteger cada assinatura manual restante com
takeUntilDestroyedpara eliminar os vazamentos.
Tags
Compartilhar
Artigos relacionados

Angular 20 em 2026: Resource API, httpResource e perguntas de entrevista
O Angular 20 introduz o httpResource e estabiliza a Resource API para a busca de dados baseada em signals. Um tutorial prático sobre resource(), rxResource(), httpResource(), a validação com Zod e as perguntas de entrevista mais comuns.

Perguntas de entrevista Angular 19: Signals, SSR e conceitos essenciais
As perguntas de entrevista Angular 19 mais comuns: Signals, hidratação incremental, detecção de mudanças sem Zone.js e novas APIs reativas com exemplos de código e respostas esperadas.

Angular 19 Zoneless: Performance e Change Detection Sem Zone.js em 2026
Guia completo sobre Angular zoneless change detection: como funciona o provideZonelessChangeDetection, migracao de Zone.js para signals, armadilhas com setTimeout e formularios reativos, SSR sem Zone.js, benchmarks de performance e o roadmap do Angular 19 ao 21.