Cuándo no usar Effects en Angular para optimizar

Cuándo (no) usar Effects en Angular y qué hacer en su lugar

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. Usa computed o 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?

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.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *