Angular 21 de Cero a Experto / Signals a fondo
computed: el valor derivado que se recalcula solo
La mitad de tu estado no es estado: es DERIVADO. El total es la suma de los gastos; el saldo
es el presupuesto menos el total; "¿hay gastos?" es gastos.length > 0. Guardar eso como
estado aparte es pedir bugs (se desincroniza). computed() es una señal calculada a partir
de otras señales: la declaras una vez y se recalcula sola, exactamente cuando hace falta.
Hoy la dominas y —cómo no— construyes tu propia versión.
Lo derivado se calcula, no se guarda
computed() en acción
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-bolsillo',
template: `
<p>Gastos: {{ cantidad() }}</p>
<p>Total: {{ total() }} COP</p>
<p>Saldo: {{ saldo() }} COP</p>
`,
})
export class BolsilloComponent {
gastos = signal([
{ nombre: 'Café', valor: 4500 },
{ nombre: 'Almuerzo', valor: 18000 },
]);
presupuesto = signal(100000);
// señales DERIVADAS: se recalculan solas cuando gastos/presupuesto cambian
cantidad = computed(() => this.gastos().length);
total = computed(() => this.gastos().reduce((s, g) => s + g.valor, 0));
saldo = computed(() => this.presupuesto() - this.total());
}Fíjate en saldo: depende de presupuesto y de total, y total a su vez depende de
gastos. Es un GRAFO de dependencias. Si agregas un gasto (gastos.update(...)), Angular
recalcula total, luego saldo, y actualiza solo las líneas del template que cambiaron.
Tú nunca llamas a "recalcular": pasa solo.
Perezoso y memoizado
Regla de oro: no dupliques estado
// ❌ MAL: total como estado aparte → se desincroniza
gastos = signal<Gasto[]>([]);
total = signal(0);
agregar(g: Gasto) {
this.gastos.update((gs) => [...gs, g]);
this.total.set(this.total() + g.valor); // fácil de olvidar → bug
}
// ✅ BIEN: total DERIVADO → nunca se desincroniza
gastos = signal<Gasto[]>([]);
total = computed(() => this.gastos().reduce((s, g) => s + g.valor, 0));
agregar(g: Gasto) {
this.gastos.update((gs) => [...gs, g]); // y ya: total se recalcula solo
}Si un valor SE PUEDE calcular a partir de otro, hazlo un computed, no una señal. Menos
código, cero desincronización.
Construye tu propio computed
Un computed es una señal cuyo valor es el resultado de una función, que se recalcula
cuando cambian las señales que esa función lee. Aquí está la idea, corriendo en vivo sobre
una versión mínima del sistema reactivo:
La clave está en recomputar: antes de ejecutar la función de cálculo, se pone a sí mismo
como observador. Así, cuando calcular() LEE gastos() o total(), esas señales lo
registran como dependiente. Cuando cualquiera cambie, lo volverán a llamar y el derivado se
actualiza. El saldo depende de total, que depende de gastos —y la cascada funciona
sola—. Angular hace esto con memoización y pereza reales, pero el mecanismo es este.
¿Cuál es la ventaja de `computed()` frente a `useMemo(fn, [deps])` de React?
Mini-reto
En el playground, agrega a "Mi Bolsillo": 1) una señal gastos con tres gastos que tengan
categoria; 2) un computado totalComida que sume solo los de categoría "comida";
3) un computado porcentajeComida que dependa de totalComida y total. Cambia los
gastos y confirma que los tres derivados se actualizan en cascada sin que llames a recalcular.
Qué sigue
Ya tienes valores que se recalculan solos. Pero a veces quieres REACCIONAR a un cambio con
algo que no es un valor: guardar en localStorage, hacer log, llamar a una API. Eso es un
efecto secundario, y para eso está effect() —la próxima lección, y el tercer pilar de las
señales—.