Cuando trabajas con componentes que se comunican entre sí mediante inputs y outputs, es fundamental entender cómo procesar datos derivados sin comprometer el rendimiento de tu aplicación. Aquí se aborda exactamente eso: calcular el total de un carrito de compras en Angular usando señales y el ciclo de vida ngOnChanges, evitando la mala práctica de ejecutar funciones directamente en el template.
¿Cómo fluyen los datos entre componentes con inputs y outputs?
La arquitectura parte de tres componentes organizados jerárquicamente. ListComponent funciona como nodo maestro que intermedia la comunicación entre ProductComponent y HeaderComponent [0:06].
El flujo de datos funciona así:
- ProductComponent notifica a ListComponent cada vez que un producto se agrega al carrito, usando un output.
- ListComponent mantiene una referencia mediante un signal de los productos agregados.
- ListComponent envía esa lista a HeaderComponent por medio de un input.
- HeaderComponent recibe los productos y los renderiza en el side menu.
Este patrón reactivo garantiza que cada cambio en el carrito se propague automáticamente. El ngFor dentro de ListComponent se encarga de renderizar múltiples productos, mientras que HeaderComponent muestra la lista con mejoras visuales como flex, imagen, título y precio de cada producto [0:50].
¿Por qué no deberías ejecutar funciones en el template de Angular?
Una primera aproximación para calcular el total consiste en crear un método calcTotal que recorra todos los productos del carrito usando el método reduce de JavaScript [1:30]:
typescript
calcTotal() {
return this.cart.reduce((total, product) => {
return total + product.price;
}, 0);
}
El método reduce utiliza un acumulador (la variable total) y el elemento que se itera (el producto). Suma el precio de cada producto partiendo de un valor inicial de cero. Luego, en el template, se llama con string interpolation: {{ calcTotal() }}.
Funciona correctamente: si hay cinco productos de cien cada uno, el total muestra quinientos [2:30]. Sin embargo, ejecutar funciones en el template es una mala práctica en Angular porque genera problemas de rendimiento. Angular re-evalúa esas funciones en cada ciclo de detección de cambios, lo que puede provocar efectos secundarios.
La única ejecución aceptable en el template es la llamada a un signal, ya que Angular optimiza internamente su evaluación [3:00].
¿Por qué un computed no resuelve este caso?
Un computed es una señal derivada que se recalcula automáticamente a partir de otras señales. Sería una solución ideal, pero existe un obstáculo: el input cart no llega como señal, sino como un valor directo. Por lo tanto, no es posible crear un computed a partir de él [3:25].
¿Cómo usar ngOnChanges y signals para calcular el total correctamente?
La solución con mejor rendimiento combina ngOnChanges con una señal reactiva [3:45]:
typescript
total = signal(0);
ngOnChanges(changes: SimpleChanges) {
if (changes['cart']) {
this.total.set(this.calcTotal());
}
}
El flujo es el siguiente:
- Se crea la variable
total como un signal inicializado en cero.
- Cada vez que llega un nuevo valor al input,
ngOnChanges se ejecuta.
- Se valida si el cambio corresponde al carrito con
changes['cart'].
- Si hay cambio, se actualiza la señal
total usando .set() con el resultado de calcTotal().
En el template, en lugar de llamar a la función, se usa el signal: {{ total() }}. Esto es aceptable porque Angular reconoce las señales como parte de su sistema reactivo [4:20].
¿Cuál es la mejor práctica para datos derivados en Angular?
La regla es clara: el template siempre debe recibir datos ya procesados, nunca ejecutar lógica de negocio. El patrón recomendado es procesar los datos en el ciclo de vida del componente y almacenar el resultado en una señal.
Esta solución no genera errores de rendimiento ni efectos secundarios. Los productos se siguen agregando y el total se recalcula correctamente en cada cambio, manteniendo la reactividad que Angular propone [4:55].
¿Has implementado alguna variante distinta para manejar datos derivados en tus componentes? Comparte tu enfoque y experiencia.