CodeForge

Angular 21 de Cero a Experto / RxJS y observables a gran escala

Higher-order observables y switchMap

Teoría24 min20 XP

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

switchmap.ts
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

comparacion.ts
// 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.