Angular Signals vs RxJS: Arquitetura, Reatividade e Desempenho na Prática
Uma análise arquitetural comparando o modelo de emissão contínua do RxJS com a avaliação sob demanda dos Signals, acompanhada de demonstração interativa de processamento.
Neste artigo
A gestão de estado e a resposta a eventos na interface
A construção de interfaces ricas sempre exigiu conciliar dois desafios distintos: responder a interações assíncronas que chegam pela rede ou pelo usuário e, ao mesmo tempo, manter os dados visíveis sincronizados na tela sem desperdiçar ciclos de processamento. No ecossistema Angular, essa responsabilidade coube historicamente ao RxJS, que operava em conjunto com a verificação de mudanças do Zone.js para atualizar componentes sempre que uma microtarefa era concluída.
Embora os fluxos observáveis resolvam com clareza a coordenação temporal de eventos, o seu uso para valores síncronos de tela costuma gerar complexidade de subscrição e transformações desnecessárias em momentos em que a interface sequer está pronta para pintar o próximo quadro. A introdução dos Signals no framework propõe justamente separar essas duas naturezas de problema, estabelecendo um modelo no qual o estado da interface é lido apenas quando necessário.
Para avaliar o impacto dessa diferença em projetos reais, convém examinar como cada modelo conduz a informação do ponto de origem até o elemento renderizado.
O modelo por emissão contínua do RxJS
No RxJS, a reatividade segue o padrão de fluxo contínuo orientado a eventos. Quando um Observable emite um dado, essa informação desce imediatamente pela cadeia de operadores configurada, executando cada transformação de forma sequencial e instantânea:
- Disparo na fonte: o observável de origem emite um valor, disparado por eventos como digitação, movimento do cursor ou resposta HTTP.
- Transformação imediata: operadores intermediários como
mapefilterprocessam o dado no mesmo instante da emissão, independentemente de haver uma solicitação de leitura ativa na tela. - Consumo no destino: o resultado processado chega aos assinantes registrados, atualizando variáveis ou acionando a renderização por meio de canais automáticos.
Cenários ideais para fluxos observáveis
A força do RxJS reside na manipulação explícita de tempo, cancelamento e concorrência, cenários em que estruturas síncronas simples não oferecem controle suficiente:
- Cancelamento de requisições obsoletas via
switchMapquando o usuário altera parâmetros de busca antes do término da consulta anterior; - Moderação de frequência de eventos com
debounceTimeouthrottleTime, evitando sobrecarga de chamadas em formulários dinâmicos; - Composição coordenada de múltiplas fontes assíncronas através de operadores de combinação como
combineLatesteforkJoin.
O comportamento de execução antecipada
Por ser um modelo de execução imediata, o RxJS processa todas as etapas intermediárias assim que o dado é emitido. Se um componente emitir vinte alterações sucessivas em um intervalo de poucos milissegundos, cada operador da esteira será chamado vinte vezes, mesmo que o navegador só consiga redesenhar o quadro uma única vez ao final desse período.
A avaliação sob demanda dos Signals
Os Signals adotam uma estratégia oposta, baseada em grafos de dependências com notificação de invalidação e leitura sob demanda. Em vez de calcular e propagar novos valores imediatamente, o nó emissor apenas avisa aos interessados que o dado anterior não é mais válido:
- Notificação de modificação: ao alterar o valor de um
signal(), o sistema marca os nós derivados dependentes como desatualizados, mas não executa nenhuma computação matemática naquele momento. - Leitura sob demanda: o cálculo de um nó
computed()só acontece quando um consumidor, como o próprio template durante o ciclo de desenho, solicita o seu valor atual. - Reaproveitamento de resultado: enquanto as dependências de origem permanecerem inalteradas, leituras repetidas do mesmo nó retornam o valor armazenado em memória, sem reexecutar a função de cálculo.
Essa mecânica elimina computações intermediárias desperdiçadas e assegura que a interface trabalhe sempre com dados consistentes, evitando oscilações temporárias entre nós interdependentes.
Demonstração interativa dos modelos
O simulador abaixo reproduz esse comportamento para ilustrar visualmente a diferença de processamento entre as duas abordagens.
Ao movimentar o controle deslizante Source Value, observe o comportamento dos dois contadores:
- O contador RXJS COMPUTATIONS cresce a cada movimento, registrando o processamento imediato dos operadores
mapefilterpara cada valor emitido; - O nó do Signal passa ao estado DIRTY, indicando que a informação está pendente, mas o contador SIGNAL COMPUTATIONS permanece zerado até que o botão Read by UI seja pressionado, momento em que a leitura é requisitada e o cálculo finalmente é realizado.
Mova o controle deslizante para registrar emissões e acione a leitura para observar o momento exato do cálculo.
Demonstração interativa comparando RxJS e Angular Signals. No RxJS, cada alteração executa imediatamente os operadores da esteira; nos Signals, a modificação apenas sinaliza o estado de invalidação e a computação ocorre quando a interface solicita o valor.
Execução passo a passo em sequência controlada
Para observar essa divergência em um cenário com intervalos definidos, a animação seguinte executa uma sequência de alterações de estado intercaladas com leituras de interface. O controle permite pausar a demonstração ou avançar manualmente em cada etapa:
Sequência pré-definida demonstrando a diferença de processamento entre os dois modelos.
Demonstração animada percorrendo alterações sucessivas e leituras periódicas para ilustrar a retenção de cálculos nos Signals.
Disposição estrutural dos dados
A organização das responsabilidades reflete a intenção arquitetural de cada padrão:
Estrutura estática da propagação de dados em cada abordagem.
Diagrama estático comparando a execução sequencial do RxJS com o nó de retenção dos Signals.
No fluxo sequencial do RxJS, a prioridade é a continuidade do transporte do dado através de transformações ativas. Na estrutura dos Signals, o nó computado atua como um ponto de controle que retém o processamento até que exista demanda real de consumo.
Critérios para escolha arquitetural
A coexistência dessas duas abordagens não significa redundância técnica, pois cada uma atende a requisitos operacionais distintos dentro de uma aplicação:
| Dimensão | RxJS (Observables) | Angular Signals |
|---|---|---|
| Natureza Primária | Fluxos de eventos assíncronos e contínuos | Estado de valor síncrono sob demanda |
| Estratégia de Avaliação | Eager (executa a cada emissão na fonte) | Lazy / Memoized (calcula sob leitura do consumidor) |
| Coordenação Temporal | Ampla gama de operadores (debounce, throttle, switchMap) | Não aplicável (apenas estado instantâneo) |
| Detecção de Mudanças | Baseada em Zone.js (marcação de árvore) | Granularidade fina e local (Zone-less) |
| Gerenciamento de Assinatura | Requer cancelamento explícito (takeUntilDestroyed) |
Automático via rastreamento de dependências |
| Melhor Aplicação | WebSockets, streams HTTP, digitação e filas | Estado de componentes, filtros de UI e valores calculados |
flowchart LR
Eventos["Eventos Assíncronos<br/>(HTTP / WebSockets / DOM)"] -->|RxJS Pipeline| Rx["RxJS Observable"]
Rx -->|"toSignal()"| Sig["Angular Signal"]
Sig -->|Leitura síncrona| UI["Template HTML / UI"]
UI -->|Ação do Usuário| Mut["signal.set() / update()"]
Mut -->|"toObservable()"| RxOut["RxJS Pipeline (Side Effects)"]
Aplicações recomendadas para Signals
- Estados síncronos locais de componentes, como abas selecionadas, estados de carregamento e visibilidade de painéis;
- Valores derivados com dependência direta de formulários ou filtros na tela, tirando proveito da memorização automática;
- Projetos que buscam adotar detecção de mudanças pontual sem dependência de patches globais de bibliotecas de execução.
Aplicações recomendadas para RxJS
- Eventos distribuídos no tempo, como digitação com atraso programado e conexões contínuas via WebSocket;
- Operações de entrada e saída com política de retentativa, tratamento de falhas em cascata e cancelamento preventivo;
- Orquestrações que dependem da relação cronológica exata entre eventos disparados por serviços distintos.
Integração entre os dois padrões
O Angular oferece interoperabilidade direta através do pacote @angular/core/rxjs-interop, permitindo transitar entre os dois modelos sem atritos de código:
- A função
toSignalconverte um fluxo observável em um Signal, facilitando o consumo de dados de serviços assíncronos diretamente no template; - A função
toObservabletransforma as notificações de um Signal em uma esteira reativa convencional, permitindo aplicar operadores de tempo quando a lógica de negócio exigir moderação de frequência.
Com essa composição, a arquitetura mantém a clareza de dados síncronos na camada de visualização e preserva a capacidade de orquestração assíncrona nas camadas de serviço e comunicação externa.
Para conhecer mais sobre arquitetura de sistemas e engenharia de software na prática, acesse a visão geral de Serviços de Software.