Angular NgFor, – la guía completa

En este post vas a aprender a usar la directiva NgFor de Angular para hacer un bucle sobre datos para renderizar datos o componentes. Renderizar una lista de componentes <todo-item> sería un gran caso de uso para NgFor.

Como Angular es un framework reactivo, es común ver NgFor siendo utilizado junto a observables, y por lo tanto nuestros ejemplos de código también seguirán un estilo reactivo. NgFor también soporta arrays y objetos tipo array – exploraremos cada enfoque.

¿Qué es NgFor?

NgFor es una de las directivas de Angular más utilizadas que viene con el CommonModule de Angular.

🙌 Consejo: Incluye el BrowserModule en el módulo raíz de tu app, ¡pues ya incluye el CommonModule por nosotros!

NgFor nos permite hacer un bucle sobre los datos y acceder a cada value y index – de forma muy similar a un Array ForEach normal.

La directiva NgFor también hace mucho más que hacer un bucle y darnos un valor e índice, se puede combinar con observables a través de la tubería async o mejorar nuestro rendimiento de renderizado con la función trackBy que podemos proporcionar.

Para este artículo, incluiremos otro componente ContactCardComponent en nuestro @NgModule:

Nuestro ContactCardComponent toma un único @Input de contact:

Así que ya tenemos todo preparado, ¿qué es lo siguiente?

Iterando colecciones

Ahora que nuestro ContactCardComponent está incluido en nuestro módulo, podemos configurar nuestro AppComponent para que utilice este conjunto de datos:

Como se mencionó en la introducción, aquí estoy utilizando Observable.of de RxJS para darme un flujo Observable de los resultados, esta es una buena manera de imitar una respuesta Observable, como cuando se utiliza el módulo HttpClient de Angular para devolver datos de una API.

Para en la práctica

Ahora que estamos configurados, podemos mirar en nuestra plantilla AppComponent:

@Component({ selector: 'app-root', template: ` <div class="app"> <ul> <li> <contact-card></contact-card> </li> </ul> </div> `})

Puedes ver que estoy declarando <contact-card> dentro de aquí, ya que queremos iterar nuestro conjunto de datos y poblar cada contacto a través de la configuración @Input dentro de nuestro ContactCardComponent.

Una forma en la que podríamos hacer esto es usando ngFor en el propio componente, sin embargo por simplicidad usaremos la lista desordenada. Vamos a añadir ngFor:

Aquí ocurren algunas cosas, la primera es que notarás un carácter * al principio del ngFor, ya veremos qué significa esto en la siguiente sección cuando veamos el elemento <ng-template>. En segundo lugar, vamos a crear un contexto llamado contact, utilizando un bucle «for of».

La directiva ngFor clonará el <li> y los nodos hijos. En este caso, el <contact-card> es un nodo hijo, y se «estampará» una tarjeta en el DOM para cada elemento concreto dentro de nuestra colección contacts.

🎉 ¡Descárgalo gratis!

Vaya más allá de Array ForEach. Toma confianza con métodos más avanzados como Reduce, Find, Filter, Every, Some y Map y entiende completamente cómo manejar las Estructuras de Datos de JavaScript.

  • Entienda completamente cómo manejar las Estructuras de Datos de JavaScript con operaciones inmutables
  • 31 páginas de sintaxis en profundidad, ejemplos del mundo real, consejos y trucos
  • Escribe una lógica de programación más limpia y mejor estructurada en 3 horas

✅ ¡Éxito! Revisa tu correo electrónico, disfruta.

Como bono extra, también te enviaremos algunas golosinas extra a través de algunos correos electrónicos adicionales.

Así que, ahora tenemos contact disponible como un Objeto individual, podemos pasar el contact individual al «:

<ul> <li *ngFor="let contact of contacts"> <contact-card ="contact"></contact-card> </li></ul>

Si estamos usando un array estático, o vinculando el resultado de un Observable a la plantilla, podemos dejar la plantilla como está actualmente. Sin embargo, opcionalmente podemos vincular el Observable directamente a la plantilla, lo que significa que necesitaremos la tubería async aquí para terminar las cosas:

<ul> <li *ngFor="let contact of contacts | async"> <contact-card ="contact"></contact-card> </li></ul>

Usando trackBy para claves

Si vienes de un fondo de AngularJS, seguramente habrás visto «track by» al usar un ng-repeat, y de forma similar en la tierra de React, usando key en un elemento de la colección.

¿Y qué hacen estos? Asocian los objetos, o claves, con los nodos del DOM en particular, por lo que si algo cambia o necesita ser re-renderizado, el framework puede hacerlo de manera mucho más eficiente. El ngFor de Angular utiliza por defecto la comprobación de identidad de objetos por ti, lo cual es rápido, ¡pero puede serlo más!

Aquí es donde entra en juego trackBy, vamos a añadir algo más de código y luego lo explicamos:

<ul> <li *ngFor="let contact of contacts | async; trackBy: trackById;"> <contact-card ="contact"></contact-card> </li></ul>

Aquí hemos añadido trackBy, luego le hemos dado un valor de trackById. Esta es una función que añadiremos en la clase del componente:

trackById(index, contact) { return contact.id;}

Todo lo que hace esta función es utilizar una solución de seguimiento personalizada para nuestra colección. En lugar de utilizar la identidad del objeto, aquí le estamos diciendo a Angular que utilice la propiedad única id que contiene cada objeto contact. Opcionalmente, podemos utilizar el index (que es el índice en la colección de cada objeto, es decir, 0, 1, 2, 3, 4).

Si tu API devuelve datos únicos, entonces usar eso sería una solución preferible a index – ya que el index puede estar sujeto a cambios si reordenas tu colección. El uso de un identificador único permite a Angular localizar ese nodo del DOM asociado al objeto mucho más rápido, y reutilizará el componente en el DOM en caso de que sea necesario actualizarlo – en lugar de destruirlo y reconstruirlo.

Capturando el «índice» y el «recuento»

La directiva ngFor no se limita a la iteración, sino que también nos proporciona algunas otras lindezas. Vamos a explorar index y count, dos propiedades públicas que se nos exponen en cada ngFor iteración.

Creemos otra variable llamada i, a la que asignaremos el valor de index. Angular nos expone estos valores bajo el capó, y cuando veamos la siguiente sección con el elemento <ng-template>, podremos ver cómo se componen.

Para registrar el índice, podemos simplemente interpolar i:

<ul> <li *ngFor="let contact of contacts | async; index as i;"> Index: {{ i }} <contact-card ="contact"></contact-card> </li></ul>

Esto nos dará cada índice, a partir de 0, para cada elemento de nuestra colección. Expongamos también count:

El count devolverá una longitud de colección viva, equivalente a contacts.length. Estos pueden ser opcionalmente ligados y pasados a cada componente, por ejemplo, es posible que desee registrar la longitud total de su colección en algún lugar, y también pasar el index del contacto particular en una función @Output:

Acceso al primero, al último, al impar, al par

Cuatro propiedades más expuestas por ngFor (bueno, en realidad por debajo utiliza NgForOfContext, una clase que genera cada contexto ngFor internamente). Veamos rápidamente el código fuente de esto:

Como he mencionado anteriormente, el NgForOfContext es el que construye nuestros elementos ngFor, ¡y puedes ver que en el constructor ya hemos echado un vistazo al index y al count! Lo último que tenemos que ver son los getters, que podemos explicar a partir del código fuente anterior:

  • first: devuelve true para el primer elemento de la colección, coincide con el índice con cero
  • last: devuelve true para el último elemento de la colección, coincide con el índice con el recuento total, menos uno para desplazar el «recuento» hacia abajo uno para atender a los índices basados en cero
  • par: devuelve true para los elementos pares (e.g. 2, 4) en la colección, utiliza % operador de módulo para calcular basado en el índice
  • impar: devuelve true para los elementos impares (e.g. 1, 3), simplemente invierte this.even el resultado
  • Usando esto, podemos añadir aplicar condicionalmente cosas como el estilo, o enganchar a la propiedad last para saber cuando la colección ha terminado de renderizarse.

    Para esta demostración rápida, usaremos ngClass para añadir algunos estilos a cada <li> (nótese cómo creamos más variables, al igual que index):

    Y algunos estilos:

    No demostraremos first y last, ¡ya que es bastante obvio por lo anterior cómo podemos engancharlos!

    Elemento

    Mencionamos anteriormente en este artículo que veríamos cómo entender lo que significa el * en nuestras plantillas. Este también comparte la misma sintaxis que *ngIf, que probablemente también hayas visto antes.

    Así que en esta siguiente sección, vamos a profundizar en ngFor* y el elemento <ng-template> para explicar con más detalle lo que realmente ocurre aquí.

    Cuando usamos un asterisco (*) en nuestras plantillas, estamos informando a Angular de que estamos usando una directiva estructural, que también es la sintaxis de azúcar (una bonita forma de abreviar) para usar el elemento <ng-template>.

    Y los componentes web

    Entonces, ¿qué es el elemento <ng-template>? Primero, vamos a dar un paso atrás. Vamos a retroceder para mostrar un poco de código de AngularJS aquí, tal vez hayas hecho esto antes o hayas hecho algo similar en otro framework/biblioteca:

    <script type="text/ng-template"> <div> My awesome template! </div></script>

    Esto anula el type de la etiqueta <script>, que evita que el motor JavaScript analice el contenido de la etiqueta <script>. Esto nos permite a nosotros, o a un framework como AngularJS, obtener el contenido de la etiqueta script y utilizarlo como alguna forma de plantilla HTML.

    Web Components introdujo hace unos años una nueva especificación similar a esta idea, llamada <template>:

    <template> <div> My awesome template! </div></template>

    Para coger nuestra plantilla anterior e instanciarla, haríamos esto en JavaScript plano:

    Nota cómo tenemos id=host, que es nuestro Nodo «anfitrión» para que la plantilla sea inyectada.

    Es posible que hayas visto este término flotando por Angular de varias maneras, como _nghost prefijos en Nodos (ng-host) o la propiedad host en directivas.

    ngFor y ng-template

    En primer lugar, <ng-template> es la implementación propia de Angular de la etiqueta <template>, lo que nos permite pensar en el diseño de aplicaciones en componentes web y las ideas que hay detrás. También nos proporciona más potencia que la que nos da el elemento <template> por defecto, encajando perfectamente en la forma en que Angular compila nuestro código.

    Entonces, ¿cómo nos dice la explicación anterior de <template> más sobre ngFor y el *? El asterisco es la sintaxis abreviada para utilizar el elemento <ng-template>.

    Empecemos por el ejemplo básico de ngFor:

    <ul> <li *ngFor="let contact of contacts | async"> <contact-card ="contact"></contact-card> </li></ul>

    Y demostremos el equivalente <ng-template>:

    <ul> <ng-template ngFor let-contact ="contacts | async"> <li> <contact-card ="contact"></contact-card> </li> </ng-template></ul>

    ¡Eso es muy diferente! Qué está pasando aquí?

    Cuando usamos *ngFor, le estamos diciendo a Angular que esencialmente trate el elemento al que está ligado el * como una plantilla.

    El elemento <ng-template> de Angular no es un verdadero componente web (a diferencia de <template>). Simplemente refleja los conceptos detrás de él para permitirle usar <ng-template> como se pretende en la especificación. Cuando compilemos nuestro código (JiT o AoT), no veremos ningún elemento <ng-template> en el DOM. Sin embargo, esto no significa que no podamos usar cosas como el Shadow DOM, ya que siguen siendo completamente posibles.

    Continuemos, y entendamos lo que ngForlet-contact y ngForOf están haciendo arriba.¡

    Plantillas de vista incrustada y

    Lo primero es lo primero, ngFor es una directiva! Revisemos parte del código fuente:

    @Directive({selector: ''})export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {...}

    Aquí, Angular está usando selectores de atributos como el valor de selector para decirle al decorador @Directive qué atributos buscar.

    La directiva utiliza , lo que implica que hay dos atributos como selector encadenado. Entonces, ¿cómo funciona ngFor si no estamos usando ngForOf?

    El compilador de Angular transforma cualquier elemento <ng-template> y las directivas utilizadas con un asterisco (*) en vistas que son independientes de la vista del componente raíz. Esto es para que cada vista pueda ser creada múltiples veces.

    Durante la fase de compilación, tomará let contact of contacts y pondrá en mayúsculas el of, y creará una clave personalizada para crear ngForOf.

    En nuestro caso, Angular construirá una vista que crea todo desde la etiqueta <li> hacia adentro:

    <!-- view --><li> <contact-card ="contact"></contact-card></li><!-- /view -->

    También crea un contenedor de vista invisible para contener todas las instancias de la plantilla, actuando como un marcador de posición para el contenido. El contenedor de vistas que Angular ha creado esencialmente envuelve las «vistas», en nuestro caso esto es justo dentro de las etiquetas <ul>. Esto alberga todas las plantillas que son creadas por ngFor (una por cada fila).

    Una pseudo-salida podría verse así:

    ngFor crea una «vista incrustada» para cada fila, pasando por la vista que ha creado y el contexto de la fila (el índice y los datos de la fila). Esta vista incrustada se inserta entonces en el contenedor de la vista. Cuando los datos cambian, rastrea los elementos para ver si se han movido. Si se han movido, en lugar de recrear las vistas incrustadas, las mueve para que estén en la posición correcta, o las destruye si ya no existen.

    Contexto y paso de variables

    El siguiente paso es entender cómo Angular pasa el contexto a cada <contact-card>:

    <ng-template ngFor let-contact ="contacts | async"> <li> <contact-card ="contact"></contact-card> </li></ng-template>

    Así que ahora hemos entendido ngFor y ngForOf, ¿cómo asocia Angular el let-contact con el contact individual al que luego enlazamos las propiedades?

    Debido a que let-contact no tiene valor, es simplemente un atributo, aquí es donde Angular proporciona un valor «implícito», o $implicit como se llama bajo el capó.

    Mientras Angular crea cada elemento ngFor, utiliza una clase NgForOfContext junto a un EmbeddedViewRef, y pasa estas propiedades dinámicamente. Aquí tienes un pequeño fragmento del código fuente:

    Al lado de esta sección de código, también podemos ver cómo se mantienen actualizadas nuestras mencionadas propiedades index y count:

    Puedes profundizar en el código fuente de la directiva con más detalle aquí.

    Así es como podemos entonces acceder al index y al count así:

    Nota cómo estamos suministrando los valores de let-i y let-c que se exponen desde la instancia NgForRow, a diferencia de let-contact.

    Para aprender más técnicas, mejores prácticas y conocimientos de expertos del mundo real, recomiendo encarecidamente consultar mis cursos de Angular – ¡te guiarán en tu viaje para dominar Angular al máximo!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *