Cuándo no usar Effects en Angular para optimizar
Tiempo estimado de lectura: 2 min
- Usa effects para interaccionar con el mundo exterior (analítica, localStorage, librerías imperativas).
- No uses effects para propagar estado dentro del grafo reactivo; usa
computed()para relaciones derivadas. - Para cancelación/debounce o flujos temporales complejos, usa RxJS y la interoperabilidad (toObservable / toSignal).
- Audita effects: la mayoría suele ser reemplazable por computed o RxJS para evitar bugs y renders extra.
La pregunta aparece en casi todos los equipos que adoptan Signals: ¿usar effect() para todo porque “reacciona” a cambios? No. Entender cuándo (no) usar Effects en Angular y qué hacer en su lugar te evita bugs raros, renders extra y código que nadie quiere mantener.
Resumen rápido (lectores con prisa)
Qué es: effect() ejecuta código al cambiar signals que lee.
Cuándo usar: para interacciones fuera del grafo (analytics, localStorage, librerías imperativas, timers con cleanup).
Cuándo evitar: para propagar estado dentro de la app; usa computed() o RxJS según convenga.
Cómo aplicarlo: usa computed() para derivadas y RxJS (toObservable/toSignal) para debounce/cancelación.
Contexto: qué hace un effect
Un efecto se ejecuta automáticamente cuando los signals que lee cambian. Eso lo hace útil para side effects: analytics, localStorage, llamadas a librerías imperativas. Pero usarlo para sincronizar estados es un antipatrón. Si tu cambio en A debe producir un nuevo valor en B dentro de la app, usa una derivación, no un efecto.
Documentación oficial: Documentación oficial
Interop RxJS: Interop RxJS
Antipatrones comunes (y por qué duelen)
1) Sincronizar signals con effects
Código que ves en todos lados:
total = signal(0);
constructor() {
effect(() => {
this.total.set(this.precio() * this.cantidad()); // antipatrón
});
}
Problemas: escrituras desde effect requieren flags especiales, ocultan la relación entre datos y disparan detecciones de cambio y re-ejecuciones innecesarias.
2) Resetear formularios desde effects
Un ID cambia y un effect resetea el form. Funciona, pero la lógica queda dispersa: ¿dónde está la verdad del flujo? Difícil de testear, propenso a race conditions.
3) Intentar manejar debounce/switchMap dentro de effects
Effects no reemplazan a RxJS. Si necesitas cancelación, debounce o switchMap, acabarás reinventando operadores o creando fugas.
Qué usar en su lugar: computed() para estado derivado
Cuando B es función de A, declara esa relación.
total = computed(() => precio() * cantidad);
Ventajas:
- Declarativo: defines qué es total, no cuándo actualizarlo.
- Lazy: solo se recalcula si alguien lo lee.
- Memoizado: evita trabajo innecesario.
Esto reduce CD y hace el código explícito y testeable.
Para flujos asíncronos complejos: RxJS + Signals
Si tu flujo necesita debounce, cancelación o combinaciones complejas, usa RxJS y la interoperabilidad:
- Convierte signal a observable:
toObservable(signal) - Aplica operadores RxJS
- Convierte a signal si lo necesitas en templates:
toSignal(observable)
Ejemplo type-ahead:
const query$ = toObservable(querySignal);
const results$ = query$.pipe(
debounceTime(300),
switchMap(q => httpClient.get(`/search?q=${q}`))
);
const resultsSignal = toSignal(results$, { initialValue: [] });
Así no reinventas lógica de tiempo y mantienes señales limpias.
Docs de interoperabilidad: Docs de interoperabilidad
Dónde SÍ usar Effect: casos legítimos
Usa effect cuando la acción produce algo fuera del grafo de Angular.
- Logging y analíticas
effect(() => sendEvent('filter-changed', { filter: filter() })); - Sincronizar con Browser APIs
effect(() => localStorage.setItem('theme', theme())); - Librerías imperativas (Chart.js, Leaflet)
effect(() => chart.update({ data: series() })); - Timers y observers con cleanup
effect((onCleanup) => {
const id = setInterval(() => poll(), 5000);
onCleanup(() => clearInterval(id));
});
Si el resultado del effect no vuelve al estado de la app, está bien.
afterRenderEffect y manipulación del DOM
Para actualizar third-party widgets después del commit DOM, usa afterRenderEffect (evita layout thrashing y separa lectura/escritura). Útil para charts que requieren dimensiones reales del canvas antes de renderizar.
Regla práctica y checklist rápido
- Si vas a llamar a
.set()o.update()sobre otro signal desde un effect: para. Usacomputedo cambia la source-of-truth. - Si necesitas debounce, cancelación o combinaciones temporales: usa RxJS (toObservable/toSignal).
- Si el cambio sale de Angular (localStorage, analytics, DOM imperativo): effect.
- Audita effects: más del 70–90% deberían ser reemplazables por computed o RxJS.
Adoptar esta disciplina no solo mejora rendimiento; hace tu código predecible y testeable. En próximos artículos veremos cómo migrar patrones comunes de RxJS a Signals paso a paso, con ejemplos reales y pruebas unitarias.
FAQ
- ¿Puedo usar effect para sincronizar dos signals?
- ¿Por qué es mejor usar computed para valores derivados?
- ¿Cuándo debería introducir RxJS con signals?
- ¿Qué pasa si un effect escribe en otro signal?
- ¿Es acceptable resetear formularios con effects?
- ¿Cuándo usar afterRenderEffect en lugar de effect?
¿Puedo usar effect para sincronizar dos signals?
Técnicamente sí, pero es un antipatrón. Si B depende de A dentro de la app, define B como computed() en vez de escribirle desde un effect. Evitas flags especiales, renders adicionales y relaciones ocultas entre datos.
¿Por qué es mejor usar computed para valores derivados?
Porque es declarativo, lazy y memoizado. Definís la relación entre datos y solo se recalcula si alguien lee el valor, lo que reduce detecciones de cambio y hace el código más testeable.
¿Cuándo debería introducir RxJS con signals?
Cuando necesitas debounce, cancelación, switchMap o combinaciones temporales complejas. Convierte signals a observables con toObservable(), aplica operadores RxJS y, si hace falta, vuelve a signal con toSignal().
¿Qué pasa si un effect escribe en otro signal?
Genera código frágil: requiere flags especiales, oculta la relación entre datos y puede provocar re-ejecuciones innecesarias y bugs difíciles de rastrear. Mejor cambiar la fuente de la verdad o usar computed.
¿Es acceptable resetear formularios con effects?
Funciona en muchos casos, pero dispersa la lógica y dificulta testing. Puede introducir race conditions. Evalúa mover la lógica al flujo de datos principal o utilizar patrones que mantengan la fuente de la verdad clara.
¿Cuándo usar afterRenderEffect en lugar de effect?
Usa afterRenderEffect para actualizar widgets o librerías que requieren dimensiones reales del DOM después del commit. Evita layout thrashing y separa lectura/escritura del DOM imperativo.
