Dans ce post, vous allez apprendre à utiliser la directive NgFor d’Angular pour boucler sur des données afin de rendre des données ou des composants. Rendre une liste de <todo-item>
composants serait un excellent cas d’utilisation pour NgFor.
Comme Angular est un framework réactif, il est courant de voir NgFor être utilisé aux côtés d’observables, et donc nos exemples de code suivront également un style réactif. NgFor supporte également les tableaux et les objets de type tableau – nous explorerons chaque approche.
Qu’est-ce que NgFor?
NgFor est l’une des directives Angular les plus couramment utilisées qui est fournie avec le module commun d’Angular.
🙌 Conseil : incluez la
BrowserModule
dans le module racine de votre application, car elle inclut déjà laCommonModule
pour nous !
NgFor nous permet de boucler sur les données et d’accéder à chaque value
et index
– un peu comme un Array ForEach ordinaire.
La directive NgFor fait également bien plus que simplement boucler et nous donner une valeur et un index, elle peut être combinée avec des observables via le tuyau async
ou améliorer nos performances de rendu avec la fonction trackBy
que nous pouvons fournir.
Pour cet article, nous allons inclure un autre composant ContactCardComponent
dans notre @NgModule
:
Notre ContactCardComponent
prend un seul @Input
de contact
:
Alors maintenant que nous sommes tous configurés, quelle est la suite ?
Itération des collections
Maintenant que notre ContactCardComponent
est inclus dans notre module, nous pouvons configurer notre AppComponent
pour utiliser ce jeu de données :
Comme mentionné dans l’introduction, j’utilise ici Observable.of
de RxJS pour me donner un flux Observable à partir des résultats, c’est une belle façon d’imiter une réponse Observable, comme lors de l’utilisation du module HttpClient
d’Angular pour retourner les données d’une API.
Pour la pratique
Maintenant que nous sommes configurés, nous pouvons nous pencher sur notre AppComponent
template :
@Component({ selector: 'app-root', template: ` <div class="app"> <ul> <li> <contact-card></contact-card> </li> </ul> </div> `})
Vous pouvez voir que je déclare <contact-card>
à l’intérieur d’ici, car nous voulons itérer notre ensemble de données et alimenter chaque contact via la @Input
configurée à l’intérieur de notre ContactCardComponent
.
Une façon de le faire est d’utiliser ngFor
sur le composant lui-même, cependant pour la simplicité nous allons utiliser la liste non ordonnée. Ajoutons ngFor
:
<ul> <li *ngFor="let contact of contacts"> <contact-card></contact-card> </li></ul>
Il se passe plusieurs choses ici, la première vous remarquerez un caractère *
au début de la ngFor
, nous verrons ce que cela signifie dans la prochaine section, lorsque nous examinerons l’élément <ng-template>
. Deuxièmement, nous créons un contexte appelé contact
, en utilisant une boucle « for of ».
La directive ngFor
clonera le <li>
et les nœuds enfants. Dans ce cas, la <contact-card>
est un nœud enfant, et une carte sera » estampillée » dans le DOM pour chaque élément particulier à l’intérieur de notre collection contacts
.
🎉 Téléchargez-le gratuitement !
Allez au-delà de Array ForEach. Mettez-vous en confiance avec des méthodes plus avancées comme Reduce, Find, Filter, Every, Some et Map et comprenez pleinement comment gérer les structures de données JavaScript.
- Comprenez pleinement comment gérer les structures de données JavaScript avec des opérations immuables
- 31 pages de syntaxe en profondeur, des exemples réels, des conseils et des astuces
Écrire une logique de programmation plus propre et mieux structurée en 3 heures
En prime, nous vous enverrons également quelques goodies supplémentaires à travers quelques emails supplémentaires.
Alors, maintenant que nous avons contact
disponible en tant qu’objet individuel, nous pouvons passer les contact
individuels dans le « :
<ul> <li *ngFor="let contact of contacts"> <contact-card ="contact"></contact-card> </li></ul>
Si vous utilisez un tableau statique, ou si vous liez le résultat d’un Observable au modèle, vous pouvez laisser le modèle tel qu’il est actuellement. Cependant, nous pouvons facultativement lier l’Observable directement au modèle, ce qui signifie que nous aurons besoin du tuyau async
ici pour terminer les choses :
<ul> <li *ngFor="let contact of contacts | async"> <contact-card ="contact"></contact-card> </li></ul>
Utilisation de trackBy pour les clés
Si vous venez d’un contexte AngularJS, vous aurez probablement vu » track by » lors de l’utilisation d’un ng-repeat
, et de la même manière au pays de React, en utilisant key
sur un élément de collection.
Donc, que font ces éléments ? Ils associent les objets, ou clés, aux nœuds DOM particuliers, donc si quelque chose change ou doit être rendu à nouveau, le framework peut le faire beaucoup plus efficacement. La ngFor
d’Angular utilise par défaut la vérification de l’identité des objets pour vous, ce qui est rapide, mais peut être plus rapide !
C’est là que trackBy
entre en jeu, ajoutons un peu plus de code puis expliquons :
<ul> <li *ngFor="let contact of contacts | async; trackBy: trackById;"> <contact-card ="contact"></contact-card> </li></ul>
Ici nous avons ajouté trackBy
, puis lui avons donné une valeur de trackById
. Voici une fonction que nous ajouterons dans la classe du composant :
trackById(index, contact) { return contact.id;}
Tout ce que fait cette fonction, c’est utiliser une solution de suivi personnalisée pour notre collection. Au lieu d’utiliser l’identité des objets, nous disons ici à Angular d’utiliser la propriété unique id
que chaque contact
objet contient. En option, nous pouvons utiliser la propriété index
(qui est l’indice dans la collection de chaque élément, c’est-à-dire 0, 1, 2, 3, 4).
Si votre API renvoie des données uniques, alors utiliser cela serait une solution préférable à index
– car le index
peut être sujet à changement si vous réorganisez votre collection. L’utilisation d’un identifiant unique permet à Angular de localiser ce nœud DOM associé à l’objet beaucoup plus rapidement, et il réutilisera le composant dans le DOM s’il doit être mis à jour – au lieu de le détruire et de le reconstruire.
Capturer « index » et « count »
La directive ngFor
ne s’arrête pas à l’itération, elle nous fournit également quelques autres subtilités. Explorons index
et count
, deux propriétés publiques qui nous sont exposées à chaque ngFor
itération.
Créons une autre variable appelée i
, à laquelle nous affecterons la valeur de index
. Angular expose ces valeurs sous le capot pour nous, et lorsque nous regardons la section suivante avec l’élément <ng-template>
, nous pouvons voir comment elles sont composées.
Pour consigner l’index, nous pouvons simplement interpoler i
:
<ul> <li *ngFor="let contact of contacts | async; index as i;"> Index: {{ i }} <contact-card ="contact"></contact-card> </li></ul>
Cela nous donnera chaque index, à partir de 0
, pour chaque élément de notre collection. Exposons également count
:
La count
retournera une longueur de collection vivante, équivalente à contacts.length
. Ceux-ci peuvent éventuellement être liés et passés dans chaque composant, par exemple, vous pouvez souhaiter enregistrer la longueur totale de votre collection quelque part, et également passer la index
du contact particulier dans une fonction @Output
:
Accéder au premier, dernier, impair, pair
Quatre autres propriétés exposées par ngFor
(en fait, en dessous, elle utilise NgForOfContext
, une classe qui génère chaque contexte ngFor
en interne). Examinons rapidement le code source à ce sujet :
Comme je l’ai mentionné plus haut, la NgForOfContext
est ce qui construit nos éléments ngFor
, et vous pouvez voir dans la constructor
que nous avons déjà jeté un coup d’œil à index
et count
! Les dernières choses que nous devons examiner sont les getters, que nous pouvons expliquer à partir du code source ci-dessus :
- premier : renvoie
true
pour le premier élément de la collection, fait correspondre l’index avec zéro - dernier : renvoie
true
pour le dernier élément de la collection, correspond à l’index avec le compte total, moins un pour décaler le « compte » vers le bas d’un pour tenir compte des index basés sur zéro - même : renvoie
true
pour les éléments pairs (par ex.p. ex. 2, 4) dans la collection, utilise%
l’opérateur modulus pour calculer en fonction de l’index - odd : renvoie
true
pour les éléments impairs (p. ex. 1, 3), inverse simplementthis.even
résultat
En utilisant ceci, nous pouvons ajouter conditionnellement des choses telles que le style, ou accrocher dans la propriété last
pour savoir quand la collection a terminé le rendu.
Pour cette rapide démonstration, nous utiliserons ngClass
pour ajouter quelques styles à chaque <li>
(notez comment nous créons plus de variables, tout comme index
) :
Et quelques styles :
Nous ne ferons pas la démonstration de first
et last
, car il est assez évident d’après ce qui précède comment nous pouvons les brancher !
élément
Nous avons mentionné plus tôt dans cet article que nous allions chercher à comprendre ce que signifiait le *
dans nos modèles. Cela partage également la même syntaxe que *ngIf
, que vous avez probablement aussi vu auparavant.
Donc, dans cette prochaine section, nous allons plonger plus profondément sur ngFor
*
et l’élément <ng-template>
pour expliquer plus en détail ce qui se passe réellement ici.
Lorsque nous utilisons un astérisque (*
) dans nos modèles, nous informons Angular que nous utilisons une directive structurelle, qui est également une syntaxe sugar (un raccourci agréable) pour utiliser l’élément <ng-template>
.
et les composants Web
Alors, qu’est-ce que l’élément <ng-template>
? Tout d’abord, faisons un pas en arrière. Nous allons revenir en arrière pour montrer un peu de code AngularJS ici, peut-être avez-vous déjà fait cela ou fait quelque chose de similaire dans un autre framework/librairie :
<script type="text/ng-template"> <div> My awesome template! </div></script>
Ceci remplace le type
sur la balise <script>
, qui empêche le moteur JavaScript d’analyser le contenu de la balise <script>
. Cela nous permet, ou à un framework tel qu’AngularJS, de récupérer le contenu de la balise script et de l’utiliser comme une forme de modèle HTML.
Web Components a introduit il y a quelques années une nouvelle spécification similaire à cette idée, appelée <template>
:
<template> <div> My awesome template! </div></template>
Pour saisir notre modèle ci-dessus et l’instancier, nous ferions ceci en JavaScript simple :
Notez comment nous avons id=host
, qui est notre Node « hôte » pour que le modèle soit injecté dans.
Vous avez peut-être vu ce terme flotter dans Angular de quelques façons, comme les préfixes _nghost
sur les Nodes (ng-host) ou la propriété host
dans les directives.
ngFor et ng-template
Tout d’abord, <ng-template>
est la propre implémentation d’Angular de la balise <template>
, nous permettant de penser à la conception d’applications en composants web et aux idées qui les sous-tendent. Il nous offre également plus de puissance que ce que l’élément <template>
nous donne par défaut, en s’intégrant de manière transparente dans la façon dont Angular compile notre code.
Alors, en quoi l’explication ci-dessus de <template>
nous en dit plus sur ngFor
et la *
? L’astérisque est une syntaxe abrégée pour utiliser l’élément <ng-template>
.
Partons de l’exemple de base ngFor
:
<ul> <li *ngFor="let contact of contacts | async"> <contact-card ="contact"></contact-card> </li></ul>
Et démontrons l’équivalent <ng-template>
:
<ul> <ng-template ngFor let-contact ="contacts | async"> <li> <contact-card ="contact"></contact-card> </li> </ng-template></ul>
C’est très différent ! Que se passe-t-il ici ?
Lorsque nous utilisons *ngFor
, nous demandons à Angular de traiter essentiellement l’élément auquel est lié le *
comme un modèle.
L’élément
<ng-template>
d’Angular n’est pas un véritable composant Web (contrairement à<template>
). Il reflète simplement les concepts qui le sous-tendent pour vous permettre d’utiliser<ng-template>
comme il est prévu dans la spécification. Lorsque nous compilons notre code (JiT ou AoT), nous ne verrons aucun élément<ng-template>
sorti dans le DOM. Cependant, cela ne signifie pas que nous ne pouvons pas utiliser des choses comme Shadow DOM, car elles sont encore tout à fait possibles.
Poursuivons, et comprenons ce que ngFor
let-contact
et ngForOf
font ci-dessus.
For et modèles de vue intégrés
Première chose, ngFor
est une directive ! Vérifions une partie du code source :
@Directive({selector: ''})export class NgForOf<T, U extends NgIterable<T> = NgIterable<T>> implements DoCheck {...}
Ici, Angular utilise des sélecteurs d’attributs comme valeur de selector
pour indiquer au décorateur @Directive
quels attributs rechercher.
La directive utilise , ce qui implique qu’il y a deux attributs comme sélecteur enchaîné. Alors, comment fonctionne
ngFor
si nous n’utilisons pas ngForOf
?
Le compilateur d’Angular transforme tous les éléments <ng-template>
et les directives utilisées avec un astérisque (*
) en vues distinctes de la vue du composant racine. Ceci afin que chaque vue puisse être créée plusieurs fois.
Pendant la phase de compilation, il prendra
let contact of contacts
et mettra en majuscule laof
, et créera une clé personnalisée pour créerngForOf
.
Dans notre cas, Angular va construire une vue qui crée tout depuis la balise <li>
vers l’intérieur :
<!-- view --><li> <contact-card ="contact"></contact-card></li><!-- /view -->
Il crée également un conteneur de vue invisible pour contenir toutes les instances du modèle, agissant comme un placeholder pour le contenu. Le conteneur de vue qu’Angular a créé enveloppe essentiellement les » vues « , dans notre cas, c’est juste à l’intérieur des balises <ul>
. Cela abrite tous les modèles qui sont créés par ngFor
(un pour chaque ligne).
Un pseudo-sortie pourrait ressembler à ceci :
ngFor
crée une « vue embarquée » pour chaque ligne, en passant par la vue qu’elle a créée et le contexte de la ligne (l’index et les données de la ligne). Cette vue embarquée est ensuite insérée dans le conteneur de la vue. Lorsque les données changent, elle suit les éléments pour voir s’ils ont été déplacés. S’ils ont bougé, au lieu de recréer les vues embarquées, il les déplace pour être à la bonne position, ou les détruit s’ils n’existent plus.
Contexte et passage de variables
L’étape suivante consiste à comprendre comment Angular passe le contexte à chaque <contact-card>
:
<ng-template ngFor let-contact ="contacts | async"> <li> <contact-card ="contact"></contact-card> </li></ng-template>
Alors maintenant que nous avons compris ngFor
et ngForOf
, comment Angular associe-t-il let-contact
à l’individuelle contact
à laquelle nous lions ensuite la propriété ?
Parce que let-contact
n’a pas de valeur, c’est simplement un attribut, c’est ici qu’Angular fournit une valeur » implicite « , ou $implicit
comme on l’appelle sous le capot.
Alors qu’Angular crée chaque ngFor
élément, il utilise une classe NgForOfContext
aux côtés d’une EmbeddedViewRef
, et transmet ces propriétés de manière dynamique. Voici un petit extrait du code source :
A côté de cette section de code, nous pouvons également voir comment nos propriétés index
et count
susmentionnées sont maintenues à jour :
Vous pouvez creuser le code source de la directive de manière plus détaillée ici.
Voici comment nous pouvons ensuite accéder aux index
et count
comme ceci :
Notez comment nous fournissons les valeurs let-i
et let-c
qui sont exposées depuis l’instance NgForRow
, contrairement à let-contact
.
Pour apprendre plus de techniques, de meilleures pratiques et de connaissances d’experts du monde réel, je vous recommande vivement de consulter mes cours Angular – ils vous guideront tout au long de votre parcours pour maîtriser Angular au maximum !