CodeForge

Angular 21 de Cero a Experto / Signals a fondo

computed: el valor derivado que se recalcula solo

Teoría22 min20 XP

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

bolsillo.component.ts
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

antipatron.ts
// ❌ 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—.