Skip to content

Savoir utiliser la directive *ngFor

Préalablement: Ajoutons CommonModule

Pour utiliser le cette directive, vous devez d'abord inclure CommonModule dans votre application en l'ajoutant à la liste des imports dans votre fichier de module principal

ts
import { CommonModule } from '@angular/core';

@NgModule({
  declarations: [
    // Ici, le composant qui contient la directive
  ],
  imports: [
    // ... autre modules
    CommonModule
  ],
})
export class MonModule { }
import { CommonModule } from '@angular/core';

@NgModule({
  declarations: [
    // Ici, le composant qui contient la directive
  ],
  imports: [
    // ... autre modules
    CommonModule
  ],
})
export class MonModule { }

Si vous mettez pas le module, vous aurez l'erreur suivante: Can't bind to '<nom de la directive>' since it isn't a known property of 'div'

ngFor est une directive importante pour votre application. Pourquoi ? Car vous recevez généralement des données de la part du serveur, et vous devez les afficher côté frontend :

js
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <p *ngFor="let user of users">{{user.name}} : {{user.age}}</p>
  `
})
export class AppComponent {
  users: any[] = [
    {name: 'Sam', age: 45},
    {name: 'Jim', age: 33},
    {name: 'Ana', age: 17},
    {name: 'Lou', age: 4},
  ]
}
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <p *ngFor="let user of users">{{user.name}} : {{user.age}}</p>
  `
})
export class AppComponent {
  users: any[] = [
    {name: 'Sam', age: 45},
    {name: 'Jim', age: 33},
    {name: 'Ana', age: 17},
    {name: 'Lou', age: 4},
  ]
}

Ici, nous avons un tableau d'utilisateurs (directement dans le code, mais nous pouvons imaginer que ces données seront récupérées sur un serveur par la suite).

La valeur de ngFor reprend la syntaxe d'une boucle Javascript. La variable locale user n'est utilisable que dans l'élément ayant ngFor

Utiliser d'autres variables locales

Il est parfois très utiliser de connaitre l'index, par exemple, pour afficher une numérotation à chaque tour de boucle. Il existe donc 5 variables locales :

  • index : position de l'item. Commence à 0.
  • first : booléen indiquant si l'item est le premier de l'itération
  • last : booléen indiquant si l'item est le dernier de l'itération
  • even : booléen indiquant si la position de l'item est paire
  • odd : booléen indiquant si la position de l'item est impaire

Voici un code illustrant leur utilisation :

js
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  styles: [
    `
      .red {
        color: red;
      }
    `
  ],
  template: `
    <p *ngFor="let user of users ; let i = index ; let isEven = even" [ngClass]="{red: isEven}">
     N°{{i}} --> {{user.name}} : {{user.age}}
    </p>
  `
})
export class AppComponent {
  users: any[] = [
    {name: 'Sam', age: 45},
    {name: 'Jim', age: 33},
    {name: 'Ana', age: 17},
    {name: 'Lou', age: 4},
  ]
}
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  styles: [
    `
      .red {
        color: red;
      }
    `
  ],
  template: `
    <p *ngFor="let user of users ; let i = index ; let isEven = even" [ngClass]="{red: isEven}">
     N°{{i}} --> {{user.name}} : {{user.age}}
    </p>
  `
})
export class AppComponent {
  users: any[] = [
    {name: 'Sam', age: 45},
    {name: 'Jim', age: 33},
    {name: 'Ana', age: 17},
    {name: 'Lou', age: 4},
  ]
}

Les variables locales sont présentes dans ngFor. Nous déclarons des nouvelles variables pour utiliser ces fameuses variables locales. Nous mixons notre boucle avec d'autres directives. Ici, lorsque l'index est pair alors l'élément est coloré en rouge.

Utiliser trackBy pour améliorer la performance

Nous allons utiliser un exemple pour se rendre compte de l'utilité de trackBy.

js
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <p *ngFor="let user of users">
      <user>
        {{user.name}} : {{user.age}}
      </user>
    </p>
    <button (click)="add()">Ajouter</button>
  `
})
export class AppComponent {

  users: any[] = [
    {name: 'Sam', age: 45, id: 1},
    {name: 'Jim', age: 33, id: 2},
    {name: 'Ana', age: 17, id: 3},
    {name: 'Lou', age: 4,  id: 4},
  ];

  add() {
    let newIndex = this.users.length+1;
    this.users = this.users.map((obj) => {
      return Object.assign({}, obj);
    })
    this.users.push({name: `Test${newIndex}`, age: 15, id: newIndex});
  }
}
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <p *ngFor="let user of users">
      <user>
        {{user.name}} : {{user.age}}
      </user>
    </p>
    <button (click)="add()">Ajouter</button>
  `
})
export class AppComponent {

  users: any[] = [
    {name: 'Sam', age: 45, id: 1},
    {name: 'Jim', age: 33, id: 2},
    {name: 'Ana', age: 17, id: 3},
    {name: 'Lou', age: 4,  id: 4},
  ];

  add() {
    let newIndex = this.users.length+1;
    this.users = this.users.map((obj) => {
      return Object.assign({}, obj);
    })
    this.users.push({name: `Test${newIndex}`, age: 15, id: newIndex});
  }
}

Notre composant permet d'afficher les utilisateurs. Rien de nouveau. Nous avons ajouté un bouton ajoutant un nouvel utilisateur.

Le tableau users a été cloné (en créant un objet immutable). Le tableau n'étant plus le même que l'initial, Angular va supprimer tous les éléments dans le DOM et les récréer d'une manière itérative. Pour se rendre compte, nous avons créer un composant enfant :

js
import {Component} from '@angular/core';

@Component({
  selector: 'user',
  template: `
    <ng-content></ng-content>
  `
})
export class UserComponent {
  ngOnInit() {
    console.log('Utilisateur créé')
  }

  ngOnDestroy() {
    console.log('Utilisateur supprimé')
  }
}
import {Component} from '@angular/core';

@Component({
  selector: 'user',
  template: `
    <ng-content></ng-content>
  `
})
export class UserComponent {
  ngOnInit() {
    console.log('Utilisateur créé')
  }

  ngOnDestroy() {
    console.log('Utilisateur supprimé')
  }
}

Effectivement, qiand nous ajoutons un utilisateur, les logs sont appelés plusieurs fois : 4 suppressions et 5 créations ensuite. Imaginez la même chose mais avec 10000 utilisateurs...

Pour éviter cela, nous utilisons trackBy qui mémorise les items selon une propriété unique. Très généralement, cette propriété est l'ID. Voici comment nous l'utilisons :

js
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <p *ngFor="let user of users ; trackBy: trackById">
      <user>
        {{user.name}} : {{user.age}}
      </user>
    </p>
    <button (click)="add()">Ajouter</button>
  `
})
export class AppComponent {

  users: any[] = [
    {name: 'Sam', age: 45, id: 1},
    {name: 'Jim', age: 33, id: 2},
    {name: 'Ana', age: 17, id: 3},
    {name: 'Lou', age: 4,  id: 4},
  ];

  add() {
    let newIndex = this.users.length+1;
    // Create immutable object
    this.users = this.users.map((obj) => {
      return Object.assign({}, obj);
    })
    this.users.push({name: `Test${newIndex}`, age: 15, id: newIndex});
  }

  trackById(index: number, obj: any): number {
    return obj.id;
  }


}
import {Component} from '@angular/core';

@Component({
  selector: 'app',
  template: `
    <p *ngFor="let user of users ; trackBy: trackById">
      <user>
        {{user.name}} : {{user.age}}
      </user>
    </p>
    <button (click)="add()">Ajouter</button>
  `
})
export class AppComponent {

  users: any[] = [
    {name: 'Sam', age: 45, id: 1},
    {name: 'Jim', age: 33, id: 2},
    {name: 'Ana', age: 17, id: 3},
    {name: 'Lou', age: 4,  id: 4},
  ];

  add() {
    let newIndex = this.users.length+1;
    // Create immutable object
    this.users = this.users.map((obj) => {
      return Object.assign({}, obj);
    })
    this.users.push({name: `Test${newIndex}`, age: 15, id: newIndex});
  }

  trackById(index: number, obj: any): number {
    return obj.id;
  }


}

Nous ajoutons donc trackBy dans ngFor avec la méthode à appeler (ici, trackById). Cette dernière envoie l'identifiant de l'item.

Si vous testez et regardez les logs, vous remarquez que nous avons qu'une création lors d'un ajout d'un utilisateur. Bien mieux !