Esa etapa en la que todo era un Stream
Si llevas un tiempo en el ecosistema de Angular, seguro que sabes de lo que hablo. Hubo una época, no hace tanto, en la que nos bebimos el Kool-Aid de la programación reactiva hasta las trancas. Yo era el primero que llegaba a la oficina diciendo que si algo no era un observable, es que estaba mal diseñado. Recuerdo perfectamente la sensación de poder que me daba encadenar operadores: un switchMap aquí, un combineLatest allá, y de repente, ¡pum!, la magia ocurría. Me sentía como un director de orquesta gestionando flujos de datos asíncronos con una elegancia que, sinceramente, me hacía sentir mejor desarrollador de lo que probablemente era.
Pero claro, con el tiempo te das cuenta de que esa elegancia tiene un precio, y a veces es carísimo. Me he pasado noches enteras intentando debugear por qué un flujo de datos no se disparaba, solo para descubrir que un shareReplay(1) estaba haciendo de las suyas en un rincón que ni recordaba. Lo que empezó como un intento de hacer aplicaciones más robustas acabó convirtiéndose en un laberinto de lógica reactiva donde hasta para cambiar un booleano tenías que pedir permiso a tres servicios y suscribirte en el componente con el dichoso pipe async.
Mi experiencia dándome cabezazos contra el código
Hubo un proyecto en particular que me abrió los ojos. Era una aplicación de gestión bastante densa, con muchos formularios dinámicos y estados compartidos. Me puse la capa de superhéroe de RxJS y decidí que todo el estado de la aplicación iba a ser reactivo. Quería evitar las mutaciones a toda costa. El resultado fue un código que sí, era técnicamente "puro" y seguía los principios de la programación reactiva a rajatabla, pero era prácticamente ilegible para cualquier otra persona que no fuera yo (e incluso para mí mismo tres meses después).
Me di cuenta de que estábamos dedicando más tiempo a pelearnos con los operadores de RxJS que a implementar funcionalidades reales. Cada vez que entraba un junior al equipo, el proceso de onboarding era un drama. Teníamos que explicarles que para entender cómo se mostraba un simple mensaje de error, tenían que seguir el rastro de un Subject que pasaba por cinco transformaciones antes de llegar a la vista. Fue ahí cuando empecé a preguntarme: ¿De verdad necesitamos todo esto para una aplicación que, al final del día, solo muestra datos de una base de datos?
¿Por qué me parece importante simplificar?
La complejidad cognitiva es el enemigo silencioso del desarrollo de software. Cuando usamos RxJS para absolutamente todo, estamos obligando a nuestro cerebro (y al de nuestros compañeros) a mantener un modelo mental de flujos temporales que es muy difícil de procesar. No es lo mismo leer una asignación de variable que entender cuándo y por qué se va a emitir un valor en un flujo asíncrono que depende de otros tres flujos.
Sinceramente, creo que nos dejamos llevar por la moda de la "reactividad total" y nos olvidamos de que la herramienta debe adaptarse al problema, y no al revés. RxJS es increíble para gestionar eventos del DOM complejos, websockets o peticiones HTTP concurrentes. Es una navaja suiza para la asincronía. Pero usarlo para guardar si un modal está abierto o cerrado... a ver, seamos honestos, es matar moscas a cañonazos.
Lo que he aprendido (a base de errores)
Con la llegada de las Signals en Angular 17 y 18, mi forma de ver la gestión de estado cambió por completo. Me di cuenta de que gran parte de lo que hacía con BehaviorSubjects se podía resolver de una forma mucho más limpia, síncrona y predecible. He aprendido a ser mucho más pragmático. Ahora, mi regla de oro es: si no hay una necesidad real de gestionar el tiempo o múltiples emisiones asíncronas, no toques RxJS.
He vuelto a disfrutar de la simplicidad de las funciones que devuelven valores, de las variables que cambian de forma clara y de los efectos secundarios que son fáciles de rastrear. Aquí os dejo un pequeño ejemplo de lo que solía hacer frente a cómo lo enfoco ahora cuando el flujo no requiere realmente potencia reactiva:
// Antes: El laberinto de observables
nombreUsuario$ = user$.pipe(
map(u => u.name),
startWith('Invitado'),
distinctUntilChanged()
);
// Ahora (con Signals o lógica simple):
nombreUsuario = computed(() => this.user()?.name ?? 'Invitado');La diferencia no es solo sintáctica; es de intención. La segunda opción es mucho más fácil de razonar, no requiere desuscripciones manuales (que siempre se nos olvida alguna) y no tiene esa carga mental de "¿cuándo se emitirá esto?".
Reflexiones finales
No me malinterpretéis, sigo amando RxJS. Me parece una de las librerías más potentes y bien construidas del ecosistema JavaScript. Pero mi relación con ella ha madurado. Ya no es ese enamoramiento ciego donde quería meterla en todas mis citas. Ahora la reservo para las ocasiones especiales, donde realmente aporta valor.
Si estás empezando un proyecto o te encuentras refactorizando uno antiguo, te daría un consejo: no te sientas menos profesional por usar una variable simple o una Signal en lugar de un flujo complejo. Al revés, la verdadera maestría está en saber elegir la herramienta más sencilla que resuelva el problema de forma eficaz. Tu "yo" del futuro, cuando tenga que volver a ese código dentro de seis meses para arreglar un bug un viernes a las cinco de la tarde, te lo agradecerá eternamente. Menos es más, de verdad. A veces, la arquitectura más elegante es la que casi no se nota.