RxJS y su ecosistema
Dónde brilla la programación reactiva en 2026: eventos complejos, websockets y coordinación asíncrona; el papel de RxJS en Angular y NgRx, sus Subjects y schedulers, sus antipatrones y una lectura honesta de su curva de aprendizaje.
RxJS es la implementación de referencia de los observables en JavaScript y una de las librerías más influyentes de la última década. No es una moda ni un dogma: es una herramienta afiladísima para una clase concreta de problemas —la coordinación de eventos asíncronos en el tiempo— y una herramienta sobredimensionada para casi todo lo demás. Saber dónde brilla y dónde estorba, y ser honesto sobre su curva de aprendizaje, es más valioso que memorizar su centenar de operadores. Esta lección sitúa RxJS en el mapa del ecosistema de 2026.
- Identificar los escenarios donde RxJS es claramente la mejor opción.
- Conocer los
Subjectcomo puente entre lo imperativo y lo reactivo. - Ubicar RxJS en el ecosistema: Angular, NgRx, schedulers y testing.
- Reconocer sus antipatrones y valorar su curva con honestidad.
Dónde brilla de verdad
RxJS no compite con una variable de estado ni con un fetch puntual. Compite —y gana— cuando el problema tiene forma de coordinación temporal de múltiples fuentes que emiten en el tiempo. Tres familias de casos lo justifican por sí solas:
Websockets y tiempo real
Flujos que emiten para siempre, con reconexión, backpressure y multiplexado. Un websocket es un observable de manual, y el reintento con backoff es una línea.
Eventos complejos de UI
Arrastrar y soltar, gestos, autocompletado con cancelación, atajos de teclado como acordes: coreografías donde el tiempo y el orden de los eventos son la lógica.
Coordinación asíncrona
Orquestar varias peticiones con dependencias, reintentos, timeouts y cancelación en cascada. Lo que en promesas es un enredo, aquí es una tubería.
El denominador común es el tiempo como protagonista. El caso del websocket resiliente ilustra la potencia: reconexión con espera creciente en pocas líneas declarativas.
import { webSocket } from 'rxjs/webSocket';
import { retry } from 'rxjs';
const canal$ = webSocket<Mensaje>('wss://api.ejemplo.com').pipe(
retry({ delay: 1000, count: 5 }), // reconecta con espera, hasta 5 veces
);
const sub = canal$.subscribe((m) => procesar(m));
// sub.unsubscribe() cierra el socket y cancela el reintento en curso
Si tu problema es “cuando pase esto y aquello, y no antes de tanto, cancelando lo anterior”, estás describiendo un grafo de operadores. Si tu problema es “muéstrame el valor actual de este dato”, RxJS es un mazo para una chincheta, y lo verás con claridad en la lección sobre signals.
Subjects: el puente imperativo
Un Observable corriente es unidireccional: la fuente empuja, tú escuchas. Pero a veces necesitas empujar valores tú, desde código imperativo —un evento externo, un bus de mensajes—. Para eso están los Subject, que son observable y observador a la vez:
import { Subject, BehaviorSubject, ReplaySubject } from 'rxjs';
const bus$ = new Subject<Evento>(); // multicast, sin valor inicial
bus$.next({ tipo: 'abrir' }); // empujas tu mismo
const estado$ = new BehaviorSubject(0); // guarda y reparte el ultimo valor
estado$.value; // acceso sincrono al actual
const historial$ = new ReplaySubject(3); // reemite los ultimos 3 al suscribir
La familia importa. Un Subject normal es multicast: reparte la misma emisión a todos los suscriptores, a diferencia de un observable frío que ejecuta el productor por suscripción. BehaviorSubject recuerda el último valor y lo entrega de inmediato al nuevo suscriptor —es lo más parecido a “estado” que ofrece RxJS—. ReplaySubject guarda un buffer. Los Subjects son también la fuente de los bugs más difíciles: al ser mutables y compartidos, es fácil convertirlos en variables globales disfrazadas.
La regla de higiene es exponer siempre la cara de solo lectura: guarda el Subject como privado y publica hacia fuera un Observable con .asObservable(), para que nadie más pueda empujar valores en él. Un Subject legítimo es un bus de eventos; un BehaviorSubject usado como fuente de verdad del estado es, casi siempre, una señal de que querías un signal.
El ecosistema en 2026
RxJS no vive solo. Angular lo lleva en el núcleo: su HttpClient, el router y los formularios exponen observables, aunque desde Angular 16 los signals ganan terreno para el estado de plantilla. NgRx construye su store Redux sobre RxJS, y sus Effects son streams que escuchan acciones y emiten nuevas. Fuera de Angular, React lo usa poco —prefiere hooks y librerías de estado del servidor—, y frameworks como SolidJS y Svelte apostaron directamente por signals. El testing tiene su joya propia:
flowchart TB RX[RxJS nucleo] --> NG[Angular HttpClient router forms] RX --> NGRX[NgRx store y effects] RX --> WS[websockets y SSE] RX --> TEST[TestScheduler y marble testing] NG --> SIG[coexiste con signals desde v16]
El TestScheduler merece mención: permite escribir tests con diagramas de canicas en texto, virtualizando el tiempo para que un test de un debounceTime de cinco segundos corra en un milisegundo. Pocas librerías asíncronas ofrecen un modelo de pruebas tan riguroso. En el mismo eje están los schedulers de ejecución —asyncScheduler, animationFrameScheduler— que te dejan decidir en qué cola del event loop emite un stream, un control fino que casi ninguna otra abstracción asíncrona expone.
Un Effect de NgRx condensa la estética del ecosistema: un stream que escucha acciones, ejecuta el trabajo asíncrono y emite una acción de resultado, con cancelación y manejo de error integrados en el mismo pipe.
cargarUsuario$ = createEffect(() =>
this.actions$.pipe(
ofType(cargarUsuario),
switchMap(({ id }) =>
this.api.usuario(id).pipe(
map((u) => usuarioOk({ usuario: u })),
catchError((e) => of(usuarioFallo({ error: e }))),
),
),
),
);
Antipatrones que delatan RxJS mal usado
La potencia de RxJS es también su filo peligroso. Cuatro señales avisan de que se está usando mal, y todas comparten raíz: tratar streams como si fueran valores o callbacks.
- Suscripciones anidadas. Un
subscribedentro de otrosubscribees casi siempre unswitchMapomergeMapsin escribir: rompe la cancelación y filtra recursos. - Subjects como estado global. Un
BehaviorSubjectexportado y mutado desde media aplicación es una variable global con esteroides; hoy eso es trabajo de un signal o un store. - Olvidar la desuscripción. El clásico: suscribirse a un stream infinito sin condición de cierre. Fuga garantizada.
switchMapen escrituras. Cancelar una petición de guardado a mitad porque llegó otra corrompe datos; ahí tocabaconcatMapoexhaustMap.
// ANTIPATRON: subscribe anidado, sin cancelacion ni composicion
usuario$.subscribe((u) => {
permisos$(u.id).subscribe((p) => aplicar(p)); // fuga y carrera
});
// CORRECTO: aplanar con el operador de concurrencia adecuado
usuario$.pipe(switchMap((u) => permisos$(u.id))).subscribe(aplicar);
Seamos honestos: RxJS es difícil, y su dificultad no es accidental. Exige un cambio de mentalidad de pull a push, un vocabulario de más de cien operadores con nombres a veces opacos, y una intuición del tiempo que solo llega con práctica. El coste no acaba en aprenderlo: el código RxJS mal escrito es notablemente más difícil de depurar que un callback torpe, porque el fallo está en la coreografía, no en una línea. La regla sana: usa RxJS donde su potencia paga su complejidad, y no lo impongas a un equipo para leer un valor de un formulario.
Para valorar RxJS hay que verlo en su momento histórico. Nació cuando la web solo tenía callbacks y el infierno de la pirámide de indentación, antes de Promise, antes de async/await, antes de AbortController y de los signals. RxJS trajo de golpe cancelación de primera clase, composición declarativa, backpressure y un álgebra del tiempo a un lenguaje que no tenía nada de eso. Buena parte de lo que hoy damos por sentado en JavaScript moderno es, conceptualmente, RxJS filtrándose a la plataforma. Esto explica su paradoja actual: es a la vez imprescindible y en retirada. Imprescindible porque para streams infinitos con coordinación temporal compleja —tiempo real, gestos, orquestación— sigue sin rival y probablemente lo siga siendo. En retirada porque muchos de sus usos históricos —una petición única, un valor de estado, una derivación simple— hoy tienen herramientas más ligeras y directas: async/await para lo puntual y signals para el estado. El profesional maduro no pregunta si RxJS es bueno o malo, sino si su problema es de la clase que justifica traer semejante artillería. Reconocer esa clase de problema —tiempo, multiplicidad, cancelación, coordinación— es la habilidad que esta lección quiere dejarte, muy por encima de cualquier operador concreto.
- Toma tres funcionalidades de una app real y clasifícalas: cuál pide RxJS, cuál basta con
async/await, cuál pide signals. - Modela un websocket con reconexión usando
retrycon backoff; describe qué pasa exactamente al desuscribir. - Sustituye un
BehaviorSubjectque usabas como estado por un signal y compara cuánto código desaparece. - Encuentra en tu base de código una suscripción anidada y reescríbela con
switchMap; explica qué bug latente eliminaste.