Operadores de Mapeamento Básico no RxJS
No RxJS, o operador map permite transformar valores emitidos por um Observable. Por exemplo, ao trabalhar com uma requisição HTTP, podemos mapear a resposta para extrair dados específicos:
const dados$ = this.servicoHttp.obterDados('/api/itens').pipe(
map(resposta => resposta.dados)
);
dados$.subscribe(resultado => console.log(resultado));
Isso converte um Observable de resposta HTTP em um Observable de dados úteis. Contudo, em cenários mais complexos, precisamos lidar com Observables de ordem superior, onde os valores mapeados são outros Observables.
Observables de Ordem Superior
Um Observable de ordem superior emite valores que são eles mesmos Observables. Isso é comum em operações como salvamentos assíncronos baseados em interações do usuário. Por exemplo, considerando um formulário Angular reativo:
@Component({
selector: 'app-formulario',
template: '<form [formGroup]="formulario">...</form>'
})
export class FormularioComponent {
formulario: FormGroup;
constructor(private construtorFormulario: FormBuilder) {
this.formulario = this.construtorFormulario.group({
nome: ['', Validators.required],
email: ['', Validators.email]
});
}
}
A propriedade this.formulario.valueChanges é um Observable que emite os valores do formulário. Para salvar esses valores no servidor, precisamos mapear cada emissão em um Observable de requisição HTTP, evitando subscrições aninhadas que complicam o código.
Estratégias de Combinação de Observables
Ao lidar com Observables de ordem superior, diferentes estratégias determinam como múltiplos Observables internos são gerenciados. As principais são concatenação, mesclagem, troca e exaustão.
Concatenação de Observables
A concatenação executa Observables em sequência, esperando que um termine antes de iniciar o próximo. Exemplo com a função concat:
const serie1$ = of('a', 'b');
const serie2$ = of('x', 'y');
const resultado$ = concat(serie1$, serie2$);
resultado$.subscribe(valor => console.log(valor)); // Saída: a, b, x, y
Isso garante ordem, ideal para operações que devem ser processadas uma após a outra.
Operador concatMap
O concatMap aplica concatenação ao mapear valores em Observables internos. No contexto de salvamento de formulário:
this.formulario.valueChanges.pipe(
concatMap(valorFormulario => this.servicoHttp.salvar('/api/dados', valorFormulario))
).subscribe(
resultado => console.log('Salvo com sucesso'),
erro => console.error('Erro ao salvar')
);
Cada requisição HTTP é disparada somente após a conclusão da anterior, garantindo processamento ordenado.
Mesclagem de Observables
A mesclagem permite a execução paralela de Observables, combinando suas emisões à medida que ocorrem. Exemplo:
const intervalo1$ = interval(1000).pipe(map(v => v * 10));
const intervalo2$ = interval(1000).pipe(map(v => v * 100));
const resultado$ = merge(intervalo1$, intervalo2$);
resultado$.subscribe(valor => console.log(valor)); // Emissões intercaladas: 0, 0, 10, 100, 20, 200...
Operador mergeMap
O mergeMap realiza mapeamento com mesclagem, permitindo múltiplos Observables internos ativos simultaneamente. Cuidado: isso pode levar a requisições concorrentes.
this.formulario.valueChanges.pipe(
mergeMap(valorFormulario => this.servicoHttp.salvar('/api/dados', valorFormulario))
).subscribe(resultado => console.log('Requisição em paralelo'));
Troca de Observables
A troca cancela o Observable interno anterior quando um novo é emitido, liberando recursos. Isso é útil para evitar trabalho desnecessário.
const fonte$ = of(1, 2, 3);
const resultado$ = fonte$.pipe(
switchMap(valor => {
const interno$ = of(valor * 10).pipe(delay(100));
return interno$;
})
);
resultado$.subscribe(console.log); // Depende do tempo; cancelamentos podem ocorrer
Operador switchMap
O switchMap é ideal para cenários como busca em tempo real, onde requisições antigas devem ser canceladas ao iniciar novas.
const termoBusca$ = fromEvent(inputElemento, 'keyup').pipe(
map(evento => evento.target.value),
debounceTime(300),
distinctUntilChanged()
);
termoBusca$.pipe(
switchMap(termo => this.servicoHttp.buscar('/api/busca', { params: { q: termo } }))
).subscribe(resultados => exibirResultados(resultados));
Isso cancela requisições pendentes ao digitar, otimizando recursos do servidor.
Exaustão de Observables
A exaustão ignora novas emissões de um Observable fonte enquanto um Observable interno ainda está ativo. Isso previne ações duplicadas, como múltiplos cliques em um botão.
Operador exhaustMap
O exhaustMap aplica exaustão ao mapeamento, útil para ações que devem ser executadas uma única vez por interação.
fromEvent(botaoSalvar, 'click').pipe(
exhaustMap(() => this.servicoHttp.salvar('/api/dados', dadosFormulario))
).subscribe(
() => console.log('Salvo'),
erro => console.error('Erro')
);
Cliques adicionais são ignorados até que a operação atual termine.
Escolhendo o Operador Correto
A seleção depende do comportamento desejado:
concatMap: para processamento sequencial garantido.mergeMap: para execução paralela, quando a ordem não é crítica.switchMap: para cancelar operações anteriores, como em buscas.exhaustMap: para ignorar ações durante operações em andamento, evitando duplicatas.
Compreender esas estratégias permite escrever código reativo mais eficiente e robusto.