Función computed() y dependencias
Los computed signals representan valores que se derivan automáticamente de otros signals. A diferencia de los signals básicos que creamos con signal()
, los computed signals se recalculan de forma reactiva cada vez que alguna de sus dependencias cambia.
La función computed() crea signals de solo lectura que ejecutan una función de cálculo cuando cualquiera de los signals que leen dentro de esa función se modifica. Este mecanismo permite crear cadenas de reactividad donde los cambios se propagan automáticamente por toda la aplicación.
Creación de computed signals
Para crear un computed signal utilizamos la función computed()
que recibe como parámetro una función de cálculo:
import { signal, computed } from '@angular/core';
const precio = signal(100);
const descuento = signal(0.1);
// Computed signal que calcula el precio final
const precioFinal = computed(() => {
return precio() * (1 - descuento());
});
console.log(precioFinal()); // 90
En este ejemplo, precioFinal
es un computed signal que depende de precio
y descuento
. Cada vez que alguno de estos signals base cambie, precioFinal
se recalculará automáticamente.
Dependencias automáticas
Una de las características más importantes de los computed signals es que Angular rastrea automáticamente qué signals se leen durante la ejecución de la función de cálculo. No necesitamos declarar explícitamente las dependencias:
const nombre = signal('Juan');
const apellido = signal('Pérez');
const edad = signal(25);
// Angular detecta automáticamente que depende de nombre y apellido
const nombreCompleto = computed(() => {
return `${nombre()} ${apellido()}`;
});
// Este computed solo depende de edad (no de nombre ni apellido)
const esAdulto = computed(() => {
return edad() >= 18;
});
Cuando nombre
o apellido
cambien, solo nombreCompleto
se recalculará. Si cambia edad
, solo esAdulto
se actualizará. Esta detección automática de dependencias hace que el sistema sea muy eficiente.
Computed signals de solo lectura
Los computed signals son inmutables por diseño. No podemos modificar directamente su valor usando métodos como set()
o update()
:
const contador = signal(0);
const doble = computed(() => contador() * 2);
// ❌ Esto causará un error
// doble.set(10); // Error: Property 'set' does not exist
// ✅ La única forma de cambiar un computed es modificar sus dependencias
contador.set(5);
console.log(doble()); // 10
Esta restricción garantiza que el flujo de datos sea unidireccional y predecible, evitando inconsistencias en el estado de la aplicación.
Dependencias múltiples y complejas
Los computed signals pueden depender de múltiples signals y realizar cálculos complejos:
const productos = signal([
{ nombre: 'Laptop', precio: 1000, cantidad: 2 },
{ nombre: 'Mouse', precio: 25, cantidad: 3 },
{ nombre: 'Teclado', precio: 75, cantidad: 1 }
]);
const impuesto = signal(0.21);
const descuentoGeneral = signal(0.05);
// Computed que calcula el total del carrito
const totalCarrito = computed(() => {
const subtotal = productos().reduce((sum, producto) => {
return sum + (producto.precio * producto.cantidad);
}, 0);
const conDescuento = subtotal * (1 - descuentoGeneral());
return conDescuento * (1 + impuesto());
});
// Computed que cuenta los artículos
const totalArticulos = computed(() => {
return productos().reduce((sum, producto) => sum + producto.cantidad, 0);
});
En este ejemplo, totalCarrito
depende de tres signals diferentes: productos
, impuesto
y descuentoGeneral
. Cualquier cambio en cualquiera de ellos provocará que se recalcule automáticamente.
Computed signals en componentes
Los computed signals son especialmente útiles en componentes para crear propiedades derivadas:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-usuario',
standalone: true,
template: `
<div class="usuario-card">
<h2>{{ nombreCompleto() }}</h2>
<p>{{ estadoMembresía() }}</p>
<div class="puntos">
<span>Puntos: {{ puntosUsuario() }}</span>
<span class="nivel">{{ nivelUsuario() }}</span>
</div>
</div>
`
})
export class UsuarioComponent {
nombre = signal('Ana');
apellido = signal('García');
puntos = signal(1250);
esPremium = signal(true);
// Computed signals que se actualizan automáticamente
nombreCompleto = computed(() => {
return `${this.nombre()} ${this.apellido()}`;
});
nivelUsuario = computed(() => {
const puntos = this.puntosUsuario();
if (puntos >= 2000) return 'Oro';
if (puntos >= 1000) return 'Plata';
return 'Bronce';
});
estadoMembresía = computed(() => {
return this.esPremium() ? 'Miembro Premium' : 'Miembro Estándar';
});
puntosUsuario = computed(() => {
const base = this.puntos();
return this.esPremium() ? base * 1.5 : base;
});
}
En este componente, todos los computed signals se actualizan automáticamente cuando cambian sus dependencias. Si esPremium
cambia de false
a true
, tanto estadoMembresía
como puntosUsuario
y nivelUsuario
se recalcularán automáticamente.
Cadenas de computed signals
Los computed signals pueden depender de otros computed signals, creando cadenas de reactividad:
const temperatura = signal(25); // Celsius
const unidad = signal('celsius');
// Primer nivel: conversión de temperatura
const temperaturaKelvin = computed(() => {
return temperatura() + 273.15;
});
const temperaturaFahrenheit = computed(() => {
return (temperatura() * 9/5) + 32;
});
// Segundo nivel: temperatura mostrada según la unidad
const temperaturaFormateada = computed(() => {
const temp = temperatura();
const unit = unidad();
switch (unit) {
case 'fahrenheit':
return `${temperaturaFahrenheit().toFixed(1)}°F`;
case 'kelvin':
return `${temperaturaKelvin().toFixed(1)}K`;
default:
return `${temp.toFixed(1)}°C`;
}
});
// Tercer nivel: mensaje basado en la temperatura
const mensajeClima = computed(() => {
const tempC = temperatura();
if (tempC > 30) return 'Hace calor';
if (tempC < 10) return 'Hace frío';
return 'Temperatura agradable';
});
En esta cadena, cuando temperatura
cambia, se actualizan automáticamente temperaturaKelvin
, temperaturaFahrenheit
, temperaturaFormateada
y mensajeClima
en el orden correcto.
Lazy evaluation y caching
Los computed signals de Angular implementan dos optimizaciones fundamentales que mejoran significativamente el rendimiento: lazy evaluation (evaluación perezosa) y caching (almacenamiento en caché). Estas características garantizan que los cálculos solo se ejecuten cuando sea necesario y que los resultados se reutilicen eficientemente.
Lazy evaluation: cálculo bajo demanda
La evaluación perezosa significa que un computed signal no ejecuta su función de cálculo hasta que alguien realmente lee su valor. Esto evita cálculos innecesarios y mejora el rendimiento de la aplicación:
import { signal, computed } from '@angular/core';
const numeros = signal([1, 2, 3, 4, 5]);
// Este computed NO se ejecuta inmediatamente
const suma = computed(() => {
console.log('Calculando suma...');
return numeros().reduce((acc, num) => acc + num, 0);
});
const promedio = computed(() => {
console.log('Calculando promedio...');
return suma() / numeros().length;
});
// Hasta aquí, NO se han ejecutado los console.log
// Los cálculos ocurren solo cuando leemos los valores
console.log(suma()); // Ahora sí se ejecuta "Calculando suma..."
console.log(promedio()); // Se ejecuta "Calculando promedio..." pero NO "Calculando suma..." otra vez
En este ejemplo, la función de suma
solo se ejecuta cuando accedemos a suma()
. Además, cuando leemos promedio()
, este utiliza el valor ya calculado de suma
sin recalcularlo.
Sistema de caching inteligente
Los computed signals almacenan en caché el resultado de sus cálculos hasta que alguna de sus dependencias cambie. Esto significa que múltiples lecturas del mismo computed signal devuelven el valor cacheado sin ejecutar la función nuevamente:
const precio = signal(100);
const descuento = signal(0.1);
const precioFinal = computed(() => {
console.log('Ejecutando cálculo de precio final...');
return precio() * (1 - descuento());
});
// Primera lectura: ejecuta el cálculo
console.log(precioFinal()); // "Ejecutando cálculo de precio final..." → 90
// Lecturas posteriores: devuelve el valor cacheado
console.log(precioFinal()); // NO imprime el mensaje, devuelve 90
console.log(precioFinal()); // NO imprime el mensaje, devuelve 90
// Cambio en dependencia: invalida la caché
precio.set(200);
// Primera lectura después del cambio: recalcula
console.log(precioFinal()); // "Ejecutando cálculo de precio final..." → 180
console.log(precioFinal()); // NO imprime el mensaje, devuelve 180
El sistema de caching garantiza que el cálculo solo se ejecute una vez hasta que cambie alguna dependencia, optimizando el rendimiento en aplicaciones con múltiples accesos a los mismos computed signals.
Optimización en cadenas de computed signals
En cadenas de computed signals, el caching se vuelve especialmente importante para evitar recálculos en cascada innecesarios:
const datos = signal([
{ categoria: 'A', valor: 100 },
{ categoria: 'B', valor: 200 },
{ categoria: 'A', valor: 150 }
]);
const datosPorCategoria = computed(() => {
console.log('Agrupando datos por categoría...');
return datos().reduce((acc, item) => {
if (!acc[item.categoria]) acc[item.categoria] = [];
acc[item.categoria].push(item);
return acc;
}, {} as Record<string, any[]>);
});
const totalPorCategoria = computed(() => {
console.log('Calculando totales por categoría...');
const grupos = datosPorCategoria(); // Usa el valor cacheado
return Object.keys(grupos).reduce((acc, categoria) => {
acc[categoria] = grupos[categoria].reduce((sum, item) => sum + item.valor, 0);
return acc;
}, {} as Record<string, number>);
});
const resumen = computed(() => {
console.log('Generando resumen...');
const totales = totalPorCategoria(); // Usa el valor cacheado
return {
totalGeneral: Object.values(totales).reduce((sum, total) => sum + total, 0),
categorias: Object.keys(totales).length,
detalle: totales
};
});
// Primera lectura de resumen ejecuta toda la cadena una sola vez
console.log(resumen());
// "Agrupando datos por categoría..."
// "Calculando totales por categoría..."
// "Generando resumen..."
// Lecturas posteriores usan valores cacheados
console.log(resumen().totalGeneral); // Sin mensajes de consola
console.log(totalPorCategoria()); // Sin mensajes de consola
Invalidación selectiva de caché
Cuando cambia una dependencia, solo se invalida la caché de los computed signals que realmente dependen de ella, no toda la cadena:
const configuracion = signal({ tema: 'claro', idioma: 'es' });
const usuario = signal({ nombre: 'Ana', edad: 28 });
const temaActual = computed(() => {
console.log('Obteniendo tema...');
return configuracion().tema;
});
const saludo = computed(() => {
console.log('Generando saludo...');
const { nombre } = usuario();
return `Hola, ${nombre}`;
});
const interfaz = computed(() => {
console.log('Configurando interfaz...');
return {
tema: temaActual(),
mensaje: saludo()
};
});
// Primera lectura ejecuta todos los cálculos
console.log(interfaz());
// Cambio solo en configuración: invalida temaActual e interfaz, pero NO saludo
configuracion.update(config => ({ ...config, tema: 'oscuro' }));
console.log(interfaz());
// Solo imprime: "Obteniendo tema..." y "Configurando interfaz..."
// NO imprime "Generando saludo..." porque su dependencia no cambió
Beneficios de rendimiento en componentes
En aplicaciones reales, lazy evaluation y caching proporcionan mejoras significativas de rendimiento, especialmente en templates con múltiples referencias al mismo computed signal:
@Component({
selector: 'app-estadisticas',
standalone: true,
template: `
<div class="estadisticas">
<div class="card">
<h3>Resumen</h3>
<p>Total: {{ estadisticas().total }}</p>
<p>Promedio: {{ estadisticas().promedio }}</p>
</div>
<div class="card">
<h3>Distribución</h3>
@for (categoria of estadisticas().categorias; track categoria.nombre) {
<div>{{ categoria.nombre }}: {{ categoria.valor }}</div>
}
</div>
<div class="card">
<h3>Comparación</h3>
<p>Máximo: {{ estadisticas().maximo }}</p>
<p>Mínimo: {{ estadisticas().minimo }}</p>
</div>
</div>
`
})
export class EstadisticasComponent {
datos = signal([
{ nombre: 'Ventas', valor: 1500 },
{ nombre: 'Marketing', valor: 800 },
{ nombre: 'Desarrollo', valor: 1200 }
]);
// Computed con cálculo costoso que se ejecuta solo cuando es necesario
estadisticas = computed(() => {
console.log('Calculando estadísticas completas...');
const datos = this.datos();
const valores = datos.map(d => d.valor);
return {
total: valores.reduce((sum, val) => sum + val, 0),
promedio: valores.reduce((sum, val) => sum + val, 0) / valores.length,
maximo: Math.max(...valores),
minimo: Math.min(...valores),
categorias: datos.map(d => ({
nombre: d.nombre,
valor: d.valor,
porcentaje: (d.valor / valores.reduce((sum, val) => sum + val, 0)) * 100
}))
};
});
}
En este componente, aunque el template accede a estadisticas()
múltiples veces, el cálculo se ejecuta solo una vez durante el renderizado gracias al caching. Si los datos no cambian entre renderizados, el computed signal devuelve el valor cacheado sin recalcular.
Control de caching con dependencias condicionales
Los computed signals también optimizan el caching cuando tienen dependencias condicionales:
const modo = signal<'simple' | 'avanzado'>('simple');
const datosBasicos = signal([1, 2, 3, 4, 5]);
const datosAvanzados = signal([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
const procesamiento = computed(() => {
console.log('Ejecutando procesamiento...');
const modoActual = modo();
if (modoActual === 'simple') {
// Solo depende de datosBasicos en modo simple
return datosBasicos().map(x => x * 2);
} else {
// Solo depende de datosAvanzados en modo avanzado
return datosAvanzados().map(x => x * x);
}
});
// En modo simple, cambios en datosAvanzados NO invalidan la caché
console.log(procesamiento()); // modo: 'simple'
datosAvanzados.set([20, 30, 40]); // No recalcula porque no lee datosAvanzados
console.log(procesamiento()); // Usa valor cacheado
// Cambio a modo avanzado invalida la caché
modo.set('avanzado');
console.log(procesamiento()); // Recalcula porque cambió la dependencia
Esta optimización hace que Angular solo rastree las dependencias que realmente se leen durante cada ejecución, mejorando la eficiencia del sistema de reactividad.

Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Angular es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Angular
Explora más contenido relacionado con Angular y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
- Comprender qué son los computed signals y cómo se crean con la función computed().
- Entender la detección automática de dependencias y su impacto en la reactividad.
- Aprender la inmutabilidad de los computed signals y cómo modificar sus dependencias.
- Conocer la evaluación perezosa (lazy evaluation) y el caching para optimizar el rendimiento.
- Aplicar computed signals en componentes y manejar cadenas complejas de dependencias reactivas.