<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Operadores RXJS: Guia prático com casos de uso reais

O que é programação reativa com RxJS? RxJS (Reactive Extensions for JavaScript) é uma biblioteca para programação reativa que usa Observables para gerenciar sequências assíncronas de eventos. No Angular ele é nativamente integrado: toda chamada HttpClient, todo FormControl.valueChanges, todo evento do Router é um Observável. O verdadeiro poder do RxJS está em seus operadores: funções puras que transformam, filtram, combinam e gerenciam fluxos de dados. Conhecê-los bem faz a diferença entre um aplicativo reativo bem projetado e um emaranhado de retornos de chamada aninhados. 1. map — Transformando dados Analogia: Como Array.map() , mas em um fluxo contínuo ao longo do tempo. Caso de uso: Normalizar a resposta de uma API // Cenário: A API retorna { data: User[], total: number } // mas o componente espera apenas User[] this.http.get>('/api/users') .pipe( map(response => response.data), map(users => users.map(u => ({ ...u, fullName: `${u.firstName} ${u.lastName}`, avatarUrl: u.avatar || '/assets/default-avatar.png' }))) ) .subscribe(users => this.users = users); Regra prática: use map sempre que quiser transformar a forma dos dados sem alterar a taxa de saída. 2. filter — Filtrar saídas Passa apenas valores que satisfaçam um predicado booleano. Caso de uso: processar apenas eventos de teclado relevantes fromEvent(document, 'keydown') .pipe( filter(event => event.key === 'Enter' || event.key === 'Escape'), map(event => event.key) ) .subscribe(key => { if (key === 'Enter') this.confirmAction(); if (key === 'Escape') this.closeModal(); Rule regra geral: use o filtro o mais cedo possível na cadeia para reduzir o trabalho dos operadores subsequentes. 3. tap — Efeitos colaterais sem alterar o tap executa uma ação no valor atual, mas permite que o valor passe inalterado. Ideal para registrar e atualizar o estado da UI.

pipe( tap(() => this.loading = true), map(products => products.filter(p => p.inStock)), tap(products => console.log(`Produtos disponíveis: ${products.length}`)), tap(() => this.loading = false) ) .subscribe(products => this.products = products); Regra geral: não use tap para lógica de negócios, apenas para efeitos colaterais (log, análises, spinner). 4. debounceTime + distintoUntilChanged — Otimize a pesquisa Esses dois operadores geralmente trabalham em pares para evitar inundar o back-end com solicitações desnecessárias conforme o usuário digita. debounceTime(ms): espera que o usuário pare de digitar N ms antes de emitir distintoUntilChanged() : não gera saída se o valor for idêntico ao anterior Caso de uso: caixa de pesquisa com API chama this.searchControl.valueChanges.pipe( debounceTime(400), distintoUntilChanged(), filter(term => term.length >= 2), switchMap(term => this.productService.search(term)), takeUntil(this.destroy$) .subscribe(resultados => this.resultados = resultados); Sem esses operadores, cada pressionamento de tecla geraria uma solicitação HTTP. Com eles o número de ligações é reduzido em mais de 90%. 5. switchMap — Limpar solicitações anteriores switchMap é o mais usado para solicitações HTTP em resposta a eventos do usuário. Quando chega um novo problema, ele exclui automaticamente o Observable anterior e assina o novo. Caso de uso: navegação dinâmica com parâmetros de rota this.route.paramMap.pipe( map(params => params.get('id')), filter(id => !!id), switchMap(id => this.articleService.getById(id!)) .subscribe(article => this.article = article); Caso de uso: preenchimento automático com resultados atualizados // Se o usuário digitar "ang", depois "angu" e depois "angul", // somente solicitações de "angul" serão executadas searchTerm$.pipe( debounceTime(300), switchMap(term => this.searchService.getSuggestions(term)) ).subscribe(suggestions => this.suggestions = Suggestions); Regra: Use switchMap quando apenas a última resposta for relevante. 6.

mergeMap — Execução paralela Ao contrário de switchMap , mergeMap NÃO exclui solicitações anteriores: ele executa todas elas em paralelo e gera resultados à medida que chegam. Caso de uso: upload de vários arquivos de (selectedFiles).pipe( mergeMap(file => this.uploadService.upload(file).pipe( map(result => ({ file: file.name, url: result.url, status: 'ok' })), catchError(err => of({ file: file.name, status: 'error', error: err.message })) )) ).subscribe(result => this.uploadResults.push(resultado)); Cuidado: Use mergeMap(fn, maxConcurrency) para limitar solicitações paralelas simultâneas. 7. concatMap — Execução sequencial garantida concatMap executa as operações uma de cada vez em ordem, aguardando a conclusão da anterior antes de iniciar a próxima. Caso de uso: Salvando registros sequencialmente de([record1, record2, record3]).pipe( concatMap(record => this.db.save(record)) .subscribe({ next: result => console.log('Saved:', result), complete: () => console.log('All records save in order!') }); Regra de ouro dos operadores de ordem superior: apenas o último conta → switchMap Todos em paralelo → mergeMap Um de cada vez em ordem → concatMap 8. forkJoin — Aguarde múltiplas solicitações paralelas forkJoin é a contraparte reativa de Promise.all(): espera que todos os Observáveis ​​sejam concluídos e emite um objeto com os valores mais recentes de cada um. Caso de uso: carregar dados de vários endpoints para um painel ngOnInit() { forkJoin({ usuário: this.userService.getCurrent(), estatísticas: this.statsService.getDashboard(), notificações: this.notifService.getUnread(), projetos: this.projectService.getAll() }).pipe( takeUntil(this.destroy$) ).subscribe({ next: ({ usuário, estatísticas, notificações, projetos }) => { this.user = usuário; this.stats = estatísticas; this.notifications = this.isReady;

error = 'Erro ao carregar o painel' }); } Limitação: se uma das fontes gerar erro, forkJoin propaga o erro e exclui as demais. Use catchError em cada Observable para tolerar falhas parciais. 9. combineLatest — Combinando transmissões ao vivo Ao contrário de forkJoin , combineLatest não espera pela conclusão: ele recalcula a saída toda vez que uma das fontes emite um novo valor. Caso de uso: tabela filtrada com vários filtros independentes combineLatest([ this.searchControl.valueChanges.pipe(startWith('')), this.categoryControl.valueChanges.pipe(startWith('all')), this.statusControl.valueChanges.pipe(startWith('active')) ]).pipe( debounceTime(200), switchMap(([search, category, status]) => this.productService.getFiltered({ pesquisa, categoria, status }) ), takeUntil(this.destroy$) ).subscribe(produtos => this.filteredProducts = produtos); Nota: startWith() é necessário porque combineLatest não emite até que todas as fontes tenham emitido pelo menos uma vez. 10. takeUntil — Evita vazamentos de memória Toda assinatura não gerenciada é um possível vazamento de memória. takeUntil é o padrão padrão em Angular para concluir assinaturas automaticamente quando o componente é destruído. Caso de uso: padrão de limpeza padrão @Component({ ... }) export class MyComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); ngOnInit() { this.dataService.stream$.pipe( takeUntil(this.destroy$) ).subscribe(data => this.data = data); interval(5000).pipe( takeUntil(this.destroy$), switchMap(() => this.statsService.refresh()) .subscribe(stats => this.stats = stats); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } } Alternativa moderna (Angular 16+): Use takeUntilDestroyed() de @angular/core/rxjs-interop sem manipular manualmente o Assunto. 11. catchError — Tratamento de erros catchError intercepta erros no stream e permite a recuperação com um fallback observável em vez de interromper a assinatura.

Caso de uso: fallback com dados em cache + notificação do usuário this.http.get('/api/products').pipe( catchError(err => { console.error('[ProductService] API Error:', err); this.toastService.showError('Falha ao carregar produtos. Usando dados de cache.'); return of(this.cacheService.getProducts() ?? []); }) ).subscribe(produtos => this.produtos = produtos); Caso de uso: nova tentativa automática para erros de rede this.http.get('/api/critical-data').pipe( retry({ count: 3, delay: 1000 }), catchError(err => { this.errorService.report(err); return throwError(() => new Error('Serviço indisponível')); }) ).subscribe(data => this.data = data); 12. shareReplay — Cache de fluxo e compartilhamento shareReplay(1) transforma um Cold Observable em um Hot Observable compartilhado, com cache de último valor. A chamada HTTP é executada apenas uma vez, independentemente do número de assinantes. Caso de uso: configuração global carregada apenas uma vez @Injectable({ fornecidoIn: 'root' }) export class ConfigService { // a chamada HTTP é executada APENAS UMA VEZ, // todos os componentes recebem a mesma resposta armazenada em cache somente leitura config$ = this.http.get('/api/config').pipe( shareReplay(1) ); constructor(private http: HttpClient) {} } // Em qualquer componente, zero chamadas duplicadas @Component({ ... }) export class NavbarComponent { config$ = inject(ConfigService).config$; // usar cache } Resumo: Quando usar cada operador Operador Usar quando...

Mapa de exemplo típico Transformar a forma dos dados Normalizar resposta Filtro API Ignorar valores irrelevantes Filtrar eventos de toque no teclado Efeito colateral sem modificar o fluxo Logging, spinner debounceTime Aguardar pausa na entrada Caixa de pesquisa distintaUntilChanged Evitar emissões duplicadas Controles de formulário switchMap Somente a última solicitação conta Preenchimento automático, parâmetros de rota mergeMap Solicitações paralelas sem ordem Upload múltiplo concatMap Ordem de execução garantida Operações sequenciais forkJoin Aguarde N solicitações, todas concluídas Dashboard init combineLatest Várias transmissões ao vivo combinadas Vários filtros takeUntil Assinatura de limpeza automática Padrão ngOnDestroy catchError Manipular e recuperar de erros Tentar novamente + fallback shareReplay Compartilhar e armazenar fluxos de cache Configuração global Dominar esses operadores significa escrever código Angular que é mais legível, mais eficiente e praticamente livre de bugs assíncronos. O segredo é sempre partir da pergunta: “O que deve acontecer quando chega uma nova emissão enquanto a anterior ainda está em voo?” - a resposta quase sempre aponta para a operadora certa.

💬 Notas dos leitores

0 notas

Escreva uma nota

Partilhe a sua opinião, uma sugestão ou um elogio

Notas recentes

Ainda não há notas. Seja o primeiro a comentar!