Angular 21 de Cero a Experto / RxJS y observables a gran escala
Higher-order observables y switchMap
Aquí llega la parte de RxJS que separa a quien lo usa de quien lo DOMINA. ¿Qué pasa cuando
cada valor de un stream dispara OTRO Observable? Un término de búsqueda dispara una petición
HTTP; un clic dispara una descarga. Eso es un "higher-order observable" —un stream de
streams— y aplanarlo correctamente (con switchMap, mergeMap, concatMap o exhaustMap)
es la decisión que evita bugs sutiles como resultados desordenados o peticiones duplicadas.
Hoy lo entiendes a fondo.
El problema: un stream que dispara streams
Imagina la búsqueda en vivo: valueChanges emite términos ("c", "ca", "caf"), y por cada
término quieres hacer http.get('/buscar?q=caf') —que es OTRO Observable—. Si usaras map,
tendrías un Observable de Observables (un stream de peticiones sin ejecutar). Necesitas
APLANARLO: suscribirte al Observable interno y emitir sus valores. Pero, ¿qué haces con la
petición ANTERIOR cuando llega un término nuevo?
switchMap: cancela lo anterior, quédate con lo último
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs';
this.buscador.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((termino) => this.api.buscarGastos(termino)), // ← aplana + cancela
).subscribe((resultados) => this.resultados.set(resultados));switchMap hace dos cosas: (1) se suscribe al Observable interno (la petición HTTP) y emite
sus resultados; (2) si llega un valor NUEVO de la fuente antes de que el interno termine,
CANCELA el interno anterior y se cambia al nuevo. Por eso es perfecto para búsquedas:
garantiza que solo te importa el resultado de la ÚLTIMA búsqueda.
Los cuatro operadores de aplanamiento
Un ejemplo de cada uno
// BÚSQUEDA: el último término es el único que importa → switchMap
buscador.valueChanges.pipe(switchMap((q) => api.buscar(q)));
// SUBIR ARCHIVOS en paralelo, no importa el orden → mergeMap
archivos$.pipe(mergeMap((archivo) => api.subir(archivo)));
// GUARDAR cambios EN ORDEN, uno tras otro → concatMap
cambios$.pipe(concatMap((cambio) => api.guardar(cambio)));
// BOTÓN "Pagar": ignorar clics extra mientras procesa → exhaustMap
clicPagar$.pipe(exhaustMap(() => api.procesarPago()));En una búsqueda en vivo, ¿por qué se usa `switchMap` y no `mergeMap`?
Mini-reto
Para cada escenario, elige el operador de aplanamiento correcto y justifícalo: 1) un
autocompletar de direcciones; 2) un botón "Enviar pago" que no debe procesar dos veces si se
pulsa rápido; 3) guardar en orden cada edición de un documento colaborativo; 4) descargar en
paralelo las miniaturas de una galería. Escribe el pipe de RxJS de al menos dos de ellos.
Qué sigue
Ya aplanas streams anidados con criterio. Falta aprender a COMBINAR varios streams independientes —un filtro Y un término de búsqueda que juntos determinan el resultado— y a compartir un Observable entre varios suscriptores sin duplicar el trabajo. La próxima lección: combinar y multicast.