¿Cómo funciona la arquitectura actual de componentes en Angular?
Para dominar Angular, es fundamental entender cómo los componentes se comunican entre sí. En este caso, la arquitectura consta de tres nodos base, con ListComponent funcionando como un nodo maestro. Este nodo maestro gestiona la comunicación entre componentes a través de inputs y outputs.
Renderización de múltiples productos:ListComponent usa ng-for para renderizar múltiples productos.
Comunicación entre componentes:
Cada vez que un usuario añade un producto al carrito, ProductComponent envía una notificación a ListComponent por medio de un output.
ListComponent no solo mantiene una referencia de los productos en el carrito, sino que además notifica a HeaderComponent de los cambios por medio de un input.
HeaderComponent actualiza el carrito de compras en el side menu de la aplicación.
Este flujo asegura una interacción reactiva y eficiente entre los componentes. Un reto adicional ha sido mejorar el aspecto visual y calcular el total de la lista del carrito.
¿Cómo mejorar el aspecto visual y cálculo del total?
Para una mejor presentación visual, se usaron mejoras sencillas utilizando flex para organizar la imagen, título y precio del producto en un formato más ordenado y claro. Sin embargo, el objetivo final importante es mostrar el total de los productos en el carrito. Esto se puede lograr siguiendo estos pasos:
¿Cómo calcular el total de los productos?
Proponiendo un método dentro de HeaderComponent llamado CalcularTotal, se logra acumular el precio de todos los productos en el carrito. Una forma eficiente de hacerlo es usando el método reduce en JavaScript.
calcularTotal(){returnthis.carrito.reduce((total, producto)=> total + producto.price,0);}
Acumulador: Inicializado en cero y actualizándose con el precio de cada producto.
Retorno del total: Este método devuelve directamente el total acumulado.
¿Por qué evitar funciones en el template?
Aunque string interpolation en Angular permite llamar funciones directamente en el template, hacerlo puede afectar el rendimiento de la aplicación, a menos que la función sea un signAll. Para evitar problemas de rendimiento:
Mantener los datos procesados en el template: Ya preprocesados para evitar ejecución directa.
Uso de señales o computed signals: Aunque card no es una señal y no se puede usar directamente como computed, es importante gestionar cómo se recalcula el total con nuevos datos.
¿Cuál es una solución más eficiente?
Adoptar prácticas recomendadas usando ngOnChanges para recalcular el total cuando hay cambios en el input:
Propagación: Cada cambio en carrito recalcula el total.
Mejor rendimiento: Sin ejecutar funciones en el template directamente, quedando optimizado mediante señales.
Con este enfoque, Angular optimiza la interacción de componentes y mejora el rendimiento al evitar funciones directas dentro del template. Además, fomentar estas prácticas avanzadas asegura un código limpio, eficiente y escalable. Recuerda que la programación es un mundo vasto y siempre hay nuevas técnicas y estrategias por aprender. ¡Sigue explorando y ampliando tus habilidades!
Gestión de Carrito de Compras en Angular con ngOnChanges
Wooow hasta el momento el curso esta excelente. Me encanta que hayan mostrando ejemplos prácticos con ngOnDestroy() ngViewAfterInit(), constructor(), ngOnInit() y ahora ngOnChanges, sin duda el curso esta de lo mejor 💚
literal, no usaba angular desde angular 11, y he aprendido cosas que en su momento no tenia ni idea.
La preocupación presentada en el curso es válida. Llamar a funciones directamente desde el template en Angular puede ser una mala práctica si esas funciones realizan cálculos o procesamientos pesados y son llamadas frecuentemente, como cada vez que se ejecuta la detección de cambios de Angular. Esto puede llevar a problemas de rendimiento, especialmente si la función se encuentra en una parte del template que se actualiza con mucha frecuencia.
En el código que has compartido, se está utilizando el ngOnChanges lifecycle hook para actualizar el valor de total cuando cambia el cart. Este enfoque es una buena práctica porque ngOnChanges se llama solo cuando el valor de una propiedad @Input() cambia, lo que significa que calcTotal() no se ejecutará en cada ciclo de detección de cambios, sino solo cuando sea necesario.
El uso de ngOnChanges es una forma de asegurarse de que los cálculos solo se realicen cuando los datos relevantes se actualicen, en lugar de hacerlo cada vez que se detectan cambios, lo que podría ser mucho más frecuente.
Para explicar el código que has subido:
ngOnChanges es un hook del ciclo de vida de un componente Angular que se activa cuando Angular establece o restablece los datos de propiedades vinculadas a datos. Específicamente, se llama antes de ngOnInit() y cada vez que hay cambios en las propiedades de @Input() del componente.
calcTotal() es un método en tu componente que calcula la suma total de los precios en el carrito.
this.total.set(this.calcTotal()) actualiza el valor de total usando el método set de una señal reactiva (probablemente parte de una biblioteca de manejo de estado que estás utilizando).
En resumen, estás siguiendo una buena práctica al calcular el total en ngOnChanges, ya que esto asegura que la suma solo se recalcula cuando es necesario, y no en cada ciclo de detección de cambios que podría afectar al rendimiento. Esto se alinea con las prácticas recomendadas en Angular para evitar realizar trabajos innecesarios en el template y en los hooks del ciclo de vida.
así lo trabajé :)
Estaría bueno que se explicara más a profundidad por qué ejecutar funciones en el template trae concecuencias negativas en el redimiento de la aplicación.
una forma fácil es colocar un console.log() en el método y con ello podrá ver que el problema está en que se ejecuta constantemente el método.
En el caso de que hayas colocado el header en el AppComponent. el diagrama crece aún más pero es lo ideal. Creo yo que debió colocarse allí desde el inicio para asegurar la escalabilidad
Esto es lo que dice ChatGPT respecto a si es mejor implementar el GET o el ngOnChanges para calcular el total del carrito de compras:
"Para una aplicación de tamaño moderado a grande, usarngOnChanges para actualizar el total cuando el carrito cambia es probablemente la mejor opción en términos de rendimiento y escalabilidad. Para aplicaciones pequeñas o para una implementación rápida, usar un getter puede ser suficiente y más sencillo de implementar."
Ademas de lo que menciono Nicolas, se puede hacer una implementación basada en Signals, que a lo mejor no es compatible con la manera antigua de hacer Angular. Si quisieramos usar computed junto con Signals, lo podemos hacer de la siguiente manera:
import{Component, computed, input, signal }from'@angular/core';exportclassHeaderComponent{ hideSideMenu =signal(true); cart = input.required<Product[]>(); total =computed(()=>this.cart().reduce((total,{price =0})=> total + price,0));toggleSideMenu(){this.hideSideMenu.update((prevState)=>!prevState);}}```De esta manera podemos implementar los mismo usando `computed` que permite escribir menos código para conseguir el mismo resultado.
 ⚠️ **Un detalle importante de esta implementación es que se debe marcar el input como requerido** para no tener problemas de TypeScript con never. 
 
De esta manera solo haría falta modificar el template agregando la sintaxis de signal en el ciclo for y al usar el total. 
 ```html
<div *ngFor="let product of cart()">
Y cambiar el total de esta forma
<p>Total {{total() }}</p>```  ✅ De esta manera podríamos hacer la implementación con un input con signals. Esto está documentado en la página de Angular Accepting Data with Input Properties
Así voy:
Esta es la solución que plateo, no se si esta bien jeje
Y aca el resultado:
Cualquier sugerencia será muy bien recibida jeje
Algo que haría para evitar el ngOnChanges, es el SetInput
¿para calcular el total no es mejor usar un getter?
Reto:
Reto:
Yo si pude calcular el total con computed gracias a que separé el slideBar del header en componentes distintos, ya que me pareció que sileBar ameritaba su propia lógica dado que ahí cree varios métodos como:
Calcular el precio total de la compra
Calcular el total de productos(no repetidos)
Aumentar la catidad de articulos por producto
Disminuir la catidad de articulos por producto
Eliminar productos del carrito
Calcular el total de articulos
Y adicional un componente de UI para indicar que la compra es gratis una vez alcanzados los $2500 pesos
//Calculate the total cart`` totalCart = computed(() => { ``const`` cart = this.cartService.cart(); ``const`` amount = cart.reduce((sum, cart) => sum + (cart.price * (cart.quantity ?? 1)), 0); ``return`` amount; })
En el anterior video, algunos resolvieron el problema usando un get. Para cálculos simples y frecuentes, el getter es más conveniente. Para lógica más compleja o controlada sobre cambios específicos de los inputs, ngOnChanges sería más adecuado.
ngOnChanges solo se ejecuta cuando los valores de los @Input() cambian
Usar un getter es ideal cuando el valor debe recalcularse cada vez que se accede y no necesitas control explícito de los cambios.
En algunos sistemas que se deben de realizar calculos contables se tienen en cuanta otras funciones para llegar a obtener el valor total.
Utilice estos estilos y la conversion a moneda de price y total, por si alguien lo necesita
Hey les comparto una experiencia interesante de ngOnChange que nos puede evitar varios bugs en el futuro. Es su funcionamiento cuando usamos datos primitivos vs complejos en Javascript.
Verán... cuando utilizamos datos primitivos (strings, numeros, booleanos) que se pasan por valor, el funcionamiento es exactamente el que esperamos; cambiamos el valor en el componente padre y se dispara el ngOnChange hook en el componente hijo. Sin embargo, cuando pasamos datos complejos o no primitivos (functions, arrays, objects ) a través de un input y cambiamos sus datos en el padre… algo así:
Estos cambios no dispararán el ngOnChange hook del componente hijo. ¿Por qué sucede esto, si a priori estamos cambiando el objeto? Esto se debe a que ngOnChange está programado para dispararse solo cuando cambie el valor de la referencia en memoria.
La siguiente forma de añadir items al carrito asegura un cambio de referencia en memoria, por ende, la ejecución del ngOnChange hook en componentes hijos.
Hey les comparto una experiencia interesante de ngOnChange que nos puede evitar varios bugs en el futuro. Se trata de su funcionamiento cuando usamos datos primitivos vs complejos en Javascript.
Verán... cuando utilizamos datos primitivos (strings, numeros, booleanos) que se pasan por valor, el funcionamiento es exactamente el que esperamos; cambiamos el valor en el componente padre y se dispara el ngOnChange hook en el componente hijo. Sin embargo, cuando pasamos datos complejos o no primitivos (functions, arrays, objects ) a través de un input y cambiamos sus datos en el padre… algo así:
const motoBrands ="['Ducati', 'BMW', 'Honda']"motoBrands[0]='Yamaha'motoBrands.push('Suzuki')```Estos cambios no dispararán el ngOnChange hook del componente hijo. ¿Por qué sucede esto, si a priori estamos cambiando el objeto? Esto se debe a que ngOnChange está programado para dispararse solo cuando cambie el **valor de la referencia en memoria.**
La siguiente forma de añadir items al carrito asegura un cambio de referencia en memoria, por ende, la ejecución del ngOnChange hook en componentes hijos.
```js
addNewItem(item:Product){const newCart =[...this.shoppingCart.value, item]this.shoppingCart.set(newCart);}```Los invito a ver más al respecto en las siguiente clases:<https://platzi.com/new-home/clases/8617-javascript-fundamentos/66439-tipos-de-datos-mutabilidad-e-inmutabilidad/><https://platzi.com/new-home/clases/8617-javascript-fundamentos/66440-paso-por-valor/>[https://platzi.com/new-home/clases/8617-javascript-fundamentos/66441-paso-por-referencia/Espero les sea de utilidad, happy coding!](https://platzi.com/new-home/clases/8617-javascript-fundamentos/66441-paso-por-referencia/)
Mi solución la realice de la siguiente forma, agregando tambien la funcionalida de eliminar el producto }
totalProducts(products:Array<Product>){return products.reduce((totalValue,ProductValue:Product)=> totalValue +ProductValue.price,0);}ngOnChanges(changes:SimpleChanges){try{if(!Object.hasOwn(changes,"cart")){throw`No existe "cart" en el compoente Header`}const{cart}= changes
this.total.set(this.totalProducts(cart.currentValue))}catch(error){console.error(error);}}
solución al reto
<div class="p-4 bg-gray-100"><p class="text-lg font-bold">Products on the cart</p><hr class="my-4"> @for(cart of cart; track $index){<div class="my-4"><p class="text-xl font-semibold">Producto:{{cart.title}}</p><p class="text-gray-700">Precio: ${{cart.price}}</p><img src="{{cart.image}}" alt=""class="mt-2" style="width: 100px;"></div>}</div>