Capítulo 10: Fundamentos do RxJS (Observables, Observers, Subscriptions)
Capítulo 10: Fundamentos do RxJS (Observables, Observers, Subscriptions)
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual da Anatomia do RxJS
flowchart TD
Producer["Fonte Produtora de Eventos (HTTP / WebSocket / DOM)"] --> Observable["Observable (Stream Assíncrona no Tempo)"]
Observable -->|"Inscrição: .subscribe(Observer)"| Observer["Observer (Consumidor)"]
subgraph LIFECYCLE_NOTIF["Canais de Notificação do Observer"]
N["next(valor) - Emissão de Dados"]
E["error(err) - Emissão Terminal de Falha"]
C["complete() - Emissão Terminal de Sucesso"]
end
Observer --- LIFECYCLE_NOTIF
Observable -->|"Retorna Objeto de Controle"| Sub["Subscription"]
Sub -->|".unsubscribe() / takeUntilDestroyed()"| MemorySafe["Liberação de Recursos (Prevenção de Memory Leaks)"]
style Producer fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Observable fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style Observer fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style MemorySafe fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Paradigma da Programação Reativa com RxJS
Enquanto Signals são excelentes para representar o estado síncrono de uma aplicação, o RxJS (Reactive Extensions for JavaScript) é o padrão ouro absoluto para lidar com eventos assíncronos que ocorrem ao longo do tempo, como:
- Requisições HTTP e retentativas (retries).
- Fluxos de WebSockets contínuos.
- Debounce de digitação do teclado e pesquisas em tempo real.
- Orquestração de múltiplos eventos assíncronos paralelos ou sequenciais.
⚙️ 2. As Três Entidades Fundamentais: Observable, Observer e Subscription
2.1 O que é um Observable?
Um Observable é um fluxo preguiçoso (lazy) de valores ao longo do tempo. Ele não executa nenhuma ação até que algum consumidor se inscreva nele via .subscribe().
import { Observable } from 'rxjs';
// Criação manual de um Observable para entender seu funcionamento interno
const streamTempo$ = new Observable<string>(subscriber => {
console.log('Iniciando produção de dados...');
subscriber.next('Primeiro evento');
subscriber.next('Segundo evento');
const timer = setTimeout(() => {
subscriber.next('Terceiro evento (após 1 segundo)');
subscriber.complete(); // Finaliza a stream
}, 1000);
// Função de limpeza (Tear-down logic) executada no unsubscribe
return () => {
clearTimeout(timer);
console.log('Stream encerrada e recursos liberados!');
};
});
2.2 O Observer e os 3 Canais de Notificação
O Observer é o objeto que consome os dados do Observable através de 3 callbacks:
next: (dado) => void: Invocado zero ou infinitas vezes quando um novo dado chega.error: (err) => void: Notificação terminal disparada caso ocorra uma falha. Encerra a stream.complete: () => void: Notificação terminal disparada quando a stream encerra com sucesso.
// Consumindo a stream com um Observer completo
const subscricao = streamTempo$.subscribe({
next: (valor) => console.log('Dado recebido:', valor),
error: (erro) => console.error('Erro na stream:', erro),
complete: () => console.log('Stream finalizada com sucesso!')
});
// Cancelamento antecipado
// subscricao.unsubscribe();
❄️ vs 🔥 3. Cold vs Hot Observables e a Família Subject
3.1 Cold Observables
- O produtor é criado dentro do Observable para cada novo assinante.
- Cada assinante recebe sua própria execução isolada.
- Exemplo típico:
HttpClient.get()do Angular. Se dois componentes fizerem.subscribe(), duas requisições HTTP distintas serão enviadas à API.
3.2 Hot Observables e Subjects
- O produtor existe de forma independente antes mesmo de haver qualquer assinante.
- Múltiplos assinantes compartilham a mesma execução (multicasting).
- Tipos de Subjects:
Subject<T>: Não tem valor inicial nem memória. Novos assinantes só recebem dados emitidos após o momento de sua inscrição.BehaviorSubject<T>: Requer um valor inicial. Emite imediatamente seu valor atual mais recente para qualquer novo assinante.ReplaySubject<T>: Armazena um buffer com os últimos $N$ valores e os reproduz para novos inscritos.
import { BehaviorSubject } from 'rxjs';
// BehaviorSubject armazena o último estado conhecido
const usuarioAuth$ = new BehaviorSubject<{ id: number; nome: string } | null>(null);
// Assinante 1 recebe null imediatamente
usuarioAuth$.subscribe(u => console.log('Assinante 1:', u));
// Emite novo valor para todos os inscritos
usuarioAuth$.next({ id: 101, nome: 'Mariana Silva' });
// Assinante 2 se inscreve depois e recebe imediatamente o usuário atual (Mariana)
usuarioAuth$.subscribe(u => console.log('Assinante 2:', u));
🛡️ 4. Gestão Automática de Desinscrição com takeUntilDestroyed
Em aplicações Angular, inscrições manuais (.subscribe()) que nunca são canceladas permanecem vivas na memória do browser mesmo após a destruição do componente, gerando os famosos Memory Leaks.
No Angular 16+ e consolidado no 18+, utiliza-se o operador nativo takeUntilDestroyed:
import { Component, inject } from '@angular/core';
import { interval } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-monitor-pulsos',
standalone: true,
template: `<p>Monitorando pulsos em tempo real...</p>`
})
export class MonitorPulsosComponent {
constructor() {
// takeUntilDestroyed amarra o ciclo de vida do Observable à destruição deste componente!
interval(1000)
.pipe(takeUntilDestroyed())
.subscribe(tick => {
console.log(`Pulso recebido: ${tick}`);
});
}
}
⚖️ 5. Comparativo: Signals vs Observables (RxJS)
| Dimensão Técnica | Signals (signal()) |
Observables (Observable$) |
|---|---|---|
| Foco Principal | Estado de UI síncrono e valores correntes | Fluxos assíncronos e eventos no tempo |
| Inscrição Obrigatória | Não (lido de forma síncrona valor()) |
Sim (requer .subscribe() ou AsyncPipe) |
| Risco de Memory Leak | Praticamente zero | Alto se não houver cancelamento |
| Operadores Complexos | Não possui (focado em simplicidade) | Centenas (retry, switchMap, debounce) |
| Cancelamento de Ações | Não nativo | Suporte nativo a cancelamento e abort |
⚠️ 6. Armadilhas Comuns e Anti-Patterns
- Assinaturas Aninhadas (Nested Subscribes):
- Anti-Pattern: Fazer
this.http.get().subscribe(user => { this.http.getPost(user.id).subscribe(...) }). - Correção: Utilize operadores de achatamento (Higher-Order Mapping Operators) como
switchMapouconcatMap(estudados no próximo capítulo).
- Anti-Pattern: Fazer
- Esquecer de Cancelar Subscrições Infinitas:
- Observables de timers (
interval), WebSockets ou eventos de DOM nunca completam sozinhos. Sempre usetakeUntilDestroyed()ou converta para signal comtoSignal().
- Observables de timers (
📋 7. Cheat-Sheet: Síntese de RxJS Básico
// Observable Cold manual
const obs$ = new Observable(sub => { sub.next(1); sub.complete(); });
// Assinatura segura no ciclo de vida
obs$.pipe(takeUntilDestroyed()).subscribe({
next: val => console.log(val),
error: err => console.error(err)
});
// Subject com estado inicial
const state$ = new BehaviorSubject<number>(0);
state$.next(10);