Capítulo 11: Operadores Essenciais do RxJS (map, filter, switchMap, debounceTime, catchError)
Capítulo 11: Operadores Essenciais do RxJS (map, filter, switchMap, debounceTime, catchError)
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual dos 4 Operadores de Mapeamento de Ordem Superior
flowchart TD
In["Stream de Entrada: Eventos / Requisições"] --> Pipe["Pipeline .pipe()"]
subgraph FLATTENING["Estratégias de Flattening (Ordem Superior)"]
SM["switchMap: Cancela a requisição anterior e foca na mais recente"]
CM["concatMap: Enfileira e executa estritamente em sequência"]
MM["mergeMap: Executa todas simultaneamente em paralelo"]
EM["exhaustMap: Ignora novas emissões até que a atual termine"]
end
Pipe --> FLATTENING
SM -->|"Casos de Uso"| U1["Busca com Autocomplete / Typeahead"]
CM -->|"Casos de Uso"| U2["Filas de Upload / Transações Contábeis"]
MM -->|"Casos de Uso"| U3["Downloads em Lote Não Ordenados"]
EM -->|"Casos de Uso"| U4["Botão de 'Pagar Agora' (Anti-Duplo Clique)"]
style In fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Pipe fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style FLATTENING fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style U1 fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style U4 fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. O Conceito de Operadores Encadeáveis (Pipeable Operators)
Os operadores do RxJS são funções puras que recebem um Observable de entrada, aplicam transformações, filtros ou orquestrações complexas, e retornam um novo Observable. Eles são encadeados usando a função .pipe(...).
Ao combinar operadores, conseguimos resolver problemas de concorrência e processamento assíncrono em poucas linhas de código declarativo que, de outra forma, exigiriam dezenas de ifs, flags e timeouts manuais propensos a race conditions.
⚙️ 2. Operadores de Filtragem e Transformação Básicos
2.1 map e filter
Transformam e filtram os dados emitidos pela stream:
import { of } from 'rxjs';
import { map, filter } from 'rxjs/operators';
of(1, 2, 3, 4, 5, 6).pipe(
filter(n => n % 2 === 0), // Permite apenas pares: 2, 4, 6
map(n => n * 10) // Multiplica por 10: 20, 40, 60
).subscribe(console.log);
2.2 debounceTime e distinctUntilChanged
Essenciais para campos de busca e eventos de teclado:
debounceTime(300): Aguarda 300 milissegundos de silêncio antes de emitir o último caractere digitado.distinctUntilChanged(): Impede que a mesma palavra de busca seja emitida duas vezes consecutivas (ex: se o usuário pressionar a teclaCtrlou seta).
🔄 3. Operadores de Achatamento: Os “Quatro Cavaleiros” do RxJS
Quando o retorno de uma operação é outro Observable (exemplo: uma chamada HTTP), precisamos “achatar” (flatten) as streams internas:
3.1 switchMap (Busca e Autocomplete)
Cancela imediatamente a requisição HTTP em andamento caso o usuário digite um novo caractere, evitando condições de corrida (race conditions):
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';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'app-busca-livros',
standalone: true,
imports: [ReactiveFormsModule],
template: `
<input [formControl]="termoBusca" placeholder="Buscar livros..." />
`
})
export class BuscaLivrosComponent {
private http = inject(HttpClient);
termoBusca = new FormControl('');
constructor() {
this.termoBusca.valueChanges.pipe(
debounceTime(400),
distinctUntilChanged(),
switchMap(termo => {
if (!termo || termo.trim().length < 3) return of([]);
return this.http.get<any[]>(`https://api.livros.com/search?q=${termo}`).pipe(
catchError(() => of([])) // Se a busca falhar, retorna array vazio sem matar a stream do input
);
}),
takeUntilDestroyed()
).subscribe(resultados => {
console.log('Resultados recebidos da API:', resultados);
});
}
}
3.2 concatMap (Ordem Estrita e Filas)
Processa uma requisição por vez, mantendo rigorosamente a ordem em que foram disparadas. Ideal para uploads sequenciais de arquivos ou transações financeiras.
3.3 exhaustMap (Prevenção de Cliques Múltiplos)
Descarta completamente novas requisições enquanto a requisição atual estiver em andamento. Padrão ouro para botões de login ou “Finalizar Pagamento”:
import { Subject } from 'rxjs';
import { exhaustMap } from 'rxjs/operators';
const cliquePagar$ = new Subject<void>();
cliquePagar$.pipe(
exhaustMap(() => servicoPagamento.processarCobranca())
).subscribe(recibo => {
console.log('Pagamento efetuado:', recibo);
});
🛡️ 4. Tratamento de Erros e Resiliência: catchError e retry
Em RxJS, se um Observable emite um erro não tratado, a stream morre permanentemente. Para manter a aplicação resiliente:
import { catchError, retry } from 'rxjs/operators';
import { throwError, of } from 'rxjs';
this.http.get('/api/dados').pipe(
// Tenta novamente até 2 vezes antes de desistir
retry({ count: 2, delay: 1000 }),
// Intercepta e recupera o fluxo
catchError(erro => {
console.error('Falha irrecuperável na API:', erro);
// Retorna um valor fallback ou relança o erro tratado
return of({ status: 'offline', dados: [] });
})
);
⚖️ 5. Comparativo: Quando Usar Cada Operador de Mapeamento
| Operador | Comportamento com Novas Emissões | Caso de Uso Ideal |
|---|---|---|
switchMap |
Cancela a anterior e roda a nova | Autocomplete, abas/tabs ativas, rotas |
concatMap |
Enfileira em sequência FIFO | Upload ordenado, logs em lote |
mergeMap |
Executa em paralelo simultaneamente | Fetch de múltiplos IDs sem dependência |
exhaustMap |
Ignora novas até terminar a atual | Submissão de formulário, botões de checkout |
⚠️ 6. Armadilhas Comuns e Anti-Patterns
- Colocar
catchErrorno Local Errado Dentro de umswitchMap:- Erro: Colocar o
catchErrorno pipe exterior do formulário. Se a chamada HTTP falhar, a stream do input é encerrada e o usuário não consegue mais digitar nem buscar nada. - Correção: Coloque o
catchErrorno pipe interno da chamada HTTP (this.http.get().pipe(catchError(...))), retornandoof([]).
- Erro: Colocar o
- Usar
mergeMappara Operações Dependentes de Ordem:- Como o
mergeMaproda chamadas em paralelo, uma requisição mais rápida pode sobrescrever uma mais lenta, causando inconsistência de dados.
- Como o
📋 7. Cheat-Sheet: Resumo Executivo
stream$.pipe(
debounceTime(300), // Espera silêncio de 300ms
distinctUntilChanged(), // Ignora se for idêntico ao anterior
switchMap(q => buscarApi(q)), // Cancela chamadas velhas
retry(2), // Retenta até 2 vezes
catchError(e => of(fallback)) // Trata sem matar a stream
);