Crear un nuevo componente con Angular CLI:
ng generate component product-listAñadir el componente a app.ts:
- Para ello, hay que añadirlo a la lista de importaciones importar del componente.
- Después, hay que añadir el selector del componente (
app-product-list) al template (app.html).
Una forma alternativa de realizar la interpolación en Angular es vincular la propiedad title a la propiedad innerText del elemento HTML <h1>. Con esta técnica se puede modificar cualquier propiedad de un elemento HTML, no solo el texto.
El bloque @if añade o elimina un elemento HTML en el DOM basándose en la evaluación de una expresión. Si la expresión se evalúa como true, el elemento se inserta en el DOM. De lo contrario, el elemento se elimina del DOM.
Para crear la interface Product se puede usar la siguiente instrucción de Angular CLI:
ng generate interface productEl bloque @for nos permite recorrer una colección de elementos y renderizar una plantilla para cada uno, donde podemos definir marcadores de posición convenientes para interpolar los datos del elemento.
El bloque @for soporta la adición de una sección @empty, la cual se ejecuta cuando el array de elementos está vacío.
El bloque @for también soporta la directiva track, que permite a Angular identificar de manera eficiente los elementos de la lista y optimizar el renderizado. Esto es especialmente útil cuando se trabaja con listas grandes o dinámicas, ya que ayuda a evitar renderizados innecesarios y mejora el rendimiento de la aplicación.
También admite definir variables con esta estructura:
@for (product of products; track product.id; let variable=property) {}Donde property puede tomar los siguientes valores:
- $count: Indica el número de elementos en el array
- $index: Indica el índice del elemento en el array
- $first / $last: Indica si el elemento actual es el primero o el último en el array
- $even / $odd: Indica si el índice del elemento en el array es par o impar
El bloque @switch alterna entre partes de la plantilla del componente y muestra cada una dependiendo de un valor definido.
Podemos aplicar una sola clase a un elemento HTML usando la siguiente sintaxis:
<p [class.star]="isLiked"></p>En el fragmento anterior, la clase star se añadirá al elemento de párrafo cuando la expresión isLiked sea verdadera. De lo contrario, se eliminará del elemento. Si queremos aplicar múltiples clases CSS simultáneamente, podemos usar la siguiente sintaxis:
<p [class]="currentClasses"></p>La variable currentClasses es una propiedad del componente. El valor de una expresión que se usa en un enlace de clase puede ser uno de los siguientes:
- Una cadena de nombres de clase delimitada por espacios, como 'star active'.
- Un objeto con las claves como nombres de clase y los valores como condiciones booleanas para cada clave. Se añade una clase al elemento cuando el valor de la clave, con su nombre, se evalúa como verdadero. De lo contrario, la clase se elimina del elemento:
currentClasses = {
star: true,
active: false
};Al igual que el enlace de clase, podemos aplicar estilos individuales o múltiples simultáneamente usando un enlace de estilo. Se puede establecer un estilo único a un elemento HTML usando la siguiente sintaxis:
<p [style.color]="'greenyellow'"></p>En el fragmento anterior, el elemento de párrafo tendrá un color verde amarillento. Algunos estilos se pueden expandir aún más en el enlace, como el ancho del elemento de párrafo, que podemos definir junto con la unidad de medida:
<p [style.width.px]="100"></p>El elemento de párrafo tendrá 100 píxeles de largo. Si necesitamos alternar múltiples estilos a la vez, podemos usar la sintaxis de objeto:
<p [style]="currentStyles"></p>La variable currentStyles es una propiedad del componente. El valor de una expresión que se usa en un enlace de estilo puede ser uno de los siguientes:
- Una cadena con estilos separados por punto y coma, como 'color: greenyellow; width: 100px'
- Un objeto donde sus claves son los nombres de los estilos y los valores son los valores de estilo reales:
currentStyles = {
color: 'greenyellow',
width: '100px'
};La interpolación permite enviar datos desde el componente a la vista. Para enviar datos desde la vista al componente, se utilizan eventos.
Para enviar datos desde un componente padre a un componente hijo se utiliza input signal.
Con Angular CLI, crear un nuevo componente llamado product-detail:
ng generate component product-detailEl bloque @if en la plantilla del componente de detalle del producto implica que la propiedad de entrada del producto es obligatoria; de lo contrario, no muestra su título. Angular no sabe si el componente de lista de productos pasa un valor para el enlace de entrada del producto durante el tiempo de construcción. Si queremos hacer cumplir esa regla durante el tiempo de compilación, podemos definir una propiedad de entrada como obligatoria de la siguiente manera:
product = input.required<Product>();Para enviar datos desde un componente hijo a un componente padre, se utiliza output signal. Se pueden crear eventos personalizados y enviarlos con emit().
Esta técnica permite acceder a las propiedades de un componente hijo directamente en el padre.
Por defecto, Angular encapsula el CSS de un componente para que no afecte a otros componentes. Esto se logra mediante clases únicas generadas para cada componente.
En este ejemplo se crea un contenedor div con borde para el componente product-detail. Se puede comprobar que el estilo se aplica solo a este componente y no afecta a otros componentes de la aplicación. Concretamente, el div del componente app no se verá con borde. Si se inspecciona el elemento div del componente product-detail, se puede ver que tiene una clase única generada por Angular.
Este comportamiento se puede desactivar si se desea que el CSS de un componente afecte a otros componentes. Para ello, se debe establecer la propiedad encapsulation del decorador @Component a ViewEncapsulation.None. Sin embargo, esto no es recomendable, ya que puede causar conflictos de estilos entre componentes.
La detección de cambios es el mecanismo que Angular utiliza internamente para detectar los cambios que ocurren en las propiedades de los componentes y reflejar estos cambios en la vista. Se activa en eventos específicos, como cuando el usuario hace clic en un botón, se completa una solicitud asíncrona o se ejecuta un método setTimeout y setInterval. Angular utiliza un proceso llamado monkey patching para modificar dichos eventos sobrescribiendo su comportamiento predeterminado usando una biblioteca llamada Zone.js.
Cada componente tiene un detector de cambios que detecta si ha ocurrido un cambio en sus propiedades comparando el valor actual de una propiedad con el anterior. Si hay diferencias, aplica el cambio a la plantilla del componente. En el componente product-detail, cuando la propiedad de entrada del producto cambia como resultado de un evento, el mecanismo de detección de cambios se ejecuta para este componente y actualiza la plantilla en consecuencia.
Para comprobar como funciona la detección de cambios se ha creado el getter productTitle en el componente product-detail y se ha modificado el template para que lo utilice. Podemos comprobar la detección de cambios usando la herramienta profiler de Angular DevTools.
AL seleccionar un producto y pulsar sobre la barra del profiler vemos que se renderizan los siguientes componentes:
_ProductDetail | 1.5 ms | 1 instance
_ProductList | 1.0 ms | 1 instance
_App | 0.6 ms | 1 instance
_RouterOutlet | 0.4 ms | 1 instance
Si cambiamos de producto, vemos que se vuelven a renderizar los mismos componentes.
Hasta aquí todo correcto. El problema surge si pulsamos Add to cart. Al pulsar sobre la barra generada por el profiler vemos que se renderizan los siguientes componentes:
_App | 0.3 ms | 1 instance
_ProductList | 0.2 ms | 1 instance
_ProductDetail | 0.1 ms | 1 instance
_RouterOutlet | 0.1 ms | 1 instance
Vemos que todos los componentes se han vuelto a renderizar, aunque no ha habido cambios en sus propiedades. Particularmente, el componente product-detail no ha cambiado su producto, pero aún así se vuelve a renderizar.
Normalmente, esto no genera problemas de rendimiento, sin embargo, si tenemos una aplicación con muchos componentes y algunos de ellos son pesados, esto puede afectar al rendimiento de la aplicación. Se puede evitar este comportamiento usando ChangeDetectionStrategy.OnPush. Ahora, al pulsar Add to cart, el componente product-detail no se vuelve a renderizar, ya que no ha habido cambios en sus propiedades. Para que funcione el profiler correctamente se debe eliminar la caché (ctrl + F5) y recargar la página:
_ProductList | 0.6 ms | 1 instance
_App | 0.6 ms | 1 instance
_RouterOutlet | 0.1 ms | 1 instance
Los eventos de ciclo de vida son "hooks" que nos permiten intervenir en etapas específicas del ciclo de vida de un componente y aplicar lógica personalizada.
El hook de ciclo de vida ngOnInit es un método llamado durante la inicialización del componente. Todos los enlaces de entrada y las propiedades enlazadas a datos se han configurado apropiadamente en esta etapa, y podemos usarlos de forma segura. Usar el constructor del componente para acceder a ellos puede ser tentador, pero sus valores no se habrían establecido en ese momento.
El hook de ciclo de vida ngOnChanges se llama cuando una o más propiedades enlazadas a datos cambian. Este método recibe un objeto SimpleChanges que contiene las propiedades que han cambiado y sus valores anteriores y actuales. Es útil para reaccionar a cambios en las propiedades de entrada del componente.
El hook de ciclo de vida ngOnDestroy se llama justo antes de que Angular destruya el componente. Es útil para limpiar recursos, como suscripciones a observables o temporizadores, para evitar fugas de memoria.
DestroyRef es un servicio que permite a los componentes y servicios realizar limpieza de recursos de manera eficiente es una alternativa al método ngOnDestroy().
El hook de ciclo de vida ngAfterViewInit se llama después de que Angular haya inicializado completamente la vista del componente y sus vistas hijas. Es útil para realizar tareas que requieren que la vista esté completamente renderizada, como manipular el DOM o interactuar con elementos hijos.