Angular 21 de Cero a Experto / Signals a fondo
effect: reaccionar al mundo cuando las señales cambian
signal guarda estado y computed deriva valores —pero ninguno toca el mundo exterior—. A
veces necesitas HACER algo cuando una señal cambia: guardar en localStorage, escribir en
consola, sincronizar con una librería ajena. Ese "hacer algo" es un efecto secundario, y
effect() lo ejecuta automáticamente cada vez que sus señales cambian. Si vienes de React,
esto te sonará a useEffect —con una diferencia enorme que lo hace mucho más simple—.
El vigía que reacciona
effect() en acción
import { Component, signal, computed, effect } from '@angular/core';
@Component({ selector: 'app-bolsillo', template: `...` })
export class BolsilloComponent {
gastos = signal<Gasto[]>([]);
total = computed(() => this.gastos().reduce((s, g) => s + g.valor, 0));
constructor() {
// se ejecuta una vez al inicio y CADA VEZ que 'total' cambia
effect(() => {
console.log('El total ahora es', this.total());
localStorage.setItem('total', String(this.total()));
});
}
}El efecto lee this.total() dentro de su función; con eso, Angular sabe que depende de
total y lo re-ejecuta cada vez que total cambie. No declaraste ninguna dependencia:
igual que con computed, se descubren solas al leer las señales.
La gran diferencia con useEffect
Cuándo NO usar un effect (la lección clave)
Este es el error número uno con señales, heredado de React: usar un effect para calcular
un valor derivado. Si vas a derivar un valor de otras señales, eso es un computed, no un
effect.
// ❌ MAL: un effect que sincroniza una señal con otra
gastos = signal<Gasto[]>([]);
total = signal(0);
constructor() {
effect(() => this.total.set(this.gastos().reduce((s, g) => s + g.valor, 0)));
// dos fuentes de verdad, un paso extra, y estados intermedios raros
}
// ✅ BIEN: derivar es trabajo de computed
gastos = signal<Gasto[]>([]);
total = computed(() => this.gastos().reduce((s, g) => s + g.valor, 0));Usa effect solo para SALIR del mundo de las señales hacia el exterior: logging, guardar en
localStorage, sincronizar con el <title> del documento, integrar una librería que no
conoce señales, disparar analítica. Todo lo que sea "calcular un valor a partir de otros" es
un computed.
Limpieza: cuando el efecto abre algo que hay que cerrar
Si tu efecto abre un recurso (un setInterval, una suscripción), devuélvele a Angular una
función de limpieza con onCleanup —igual que el return de limpieza de useEffect—:
effect((onCleanup) => {
const id = setInterval(() => console.log(this.total()), 1000);
onCleanup(() => clearInterval(id)); // se llama antes de re-ejecutar y al destruir
});Angular además destruye el efecto automáticamente cuando el componente muere (si lo creaste en su contexto de inyección, como en el constructor). No hay fugas.
Construye tu propio effect
Ya lo tienes casi hecho de las lecciones anteriores: un effect es exactamente el
"observador" que se registra al leer señales y se re-ejecuta cuando cambian. Aquí, completo
y en vivo:
Es el mismo patrón del computed, pero en vez de guardar un valor de vuelta, ejecuta un
efecto secundario. signal, computed y effect comparten el mismo núcleo: leer registra,
escribir notifica. Con esos tres tienes toda la reactividad de Angular.
Tienes `gastos = signal([...])` y quieres una propiedad `hayGastos`. ¿Signal, computed o effect?
Mini-reto
En el playground, usa tu efecto: 1) crea una señal saldo en 100000; 2) declara un
efecto que imprima "⚠️ Saldo bajo" solo cuando `saldo() < 20000`; 3) haz varios
update restando gastos y observa que la advertencia aparece SOLA al cruzar el umbral, sin
que tú compruebes nada tras cada cambio. Nota que el efecto decide cuándo actuar según la
señal que lee.
Qué sigue
Ya tienes los tres pilares: signal, computed, effect. Ahora la pregunta grande: ¿por
qué todo esto reemplazó a zone.js y cambió la arquitectura de Angular? La próxima lección
lo explica —el salto de "revisar todo por si acaso" a "saber exactamente qué cambió"—, uno
de los paralelos legacy→moderno más importantes del framework.