Skip to content

Pourquoi et quand utiliser pure dans les pipes ?

Le code concerne la création d'un utilisateur avec un formulaire et l'affichage de la liste des utilisateurs filtrés en fonction d'une propriété de l'email dans un template Angular.

Le pipe est le suivant:

ts
import { Pipe, PipeTransform } from "@angular/core";
import { User } from "src/app/core/interfaces/user";

@Pipe({
    name: 'extFilter'
})
export class ExtensionPipe implements PipeTransform {
    transform(users: User[], ext: string): User[] {
        if (!ext) {
            return users
        }
        return users.filter(user => user.email.endsWith(ext))
    }
}
import { Pipe, PipeTransform } from "@angular/core";
import { User } from "src/app/core/interfaces/user";

@Pipe({
    name: 'extFilter'
})
export class ExtensionPipe implements PipeTransform {
    transform(users: User[], ext: string): User[] {
        if (!ext) {
            return users
        }
        return users.filter(user => user.email.endsWith(ext))
    }
}

et le composant:

ts
import { Component } from "@angular/core";
import { NgForm } from "@angular/forms";
import { User } from "src/app/core/interfaces/user";
import { UserService } from "src/app/core/services/user.service";

@Component({
    selector: 'app-users',
    template: `
        <div 
            *ngFor="let user of users | extFilter:extSelected">
            {{ user.name }} - {{ user.email }}
        </div>
        <button (click)="createUser()">Créer un utilisateur</button>
    `,
})
export class UsersComponent {
    extSelected: string = ''
    users: User[] = []

    constructor(private userService: UserService) { }

    createUser() {
        this.userService.create().subscribe((userCreated: User) => {
            this.users.push(userCreated)
        })
    }
}
import { Component } from "@angular/core";
import { NgForm } from "@angular/forms";
import { User } from "src/app/core/interfaces/user";
import { UserService } from "src/app/core/services/user.service";

@Component({
    selector: 'app-users',
    template: `
        <div 
            *ngFor="let user of users | extFilter:extSelected">
            {{ user.name }} - {{ user.email }}
        </div>
        <button (click)="createUser()">Créer un utilisateur</button>
    `,
})
export class UsersComponent {
    extSelected: string = ''
    users: User[] = []

    constructor(private userService: UserService) { }

    createUser() {
        this.userService.create().subscribe((userCreated: User) => {
            this.users.push(userCreated)
        })
    }
}

La fonction createUser() est appelée lorsque l'utilisateur soumet le formulaire de création d'utilisateur.

Problème

Si vous filtrez et que que vous cliquez sur le bouton, le pipe ne se relance pas, ce qui donne l'impression d'un bug d'affichage.

Pourquoi ?

En utilisant this.users.push(userCreated), vous ajoutez un nouvel élément à l'array users mais Angular ne détecte pas que l'array a été modifié. Il est donc nécessaire de remplacer l'array existant avec un nouveau pour que Angular puisse détecter le changement et réexécuter le pipe.

La propriété pure

Pour utiliser la propriété "pure" dans un pipe Angular, vous devez déclarer le pipe en utilisant la décoration @Pipe. La propriété pure peut être définie en utilisant l'objet de configuration de pipe, comme suit :

ts
@Pipe({
    name: 'extFilter',
    pure: true
})
@Pipe({
    name: 'extFilter',
    pure: true
})

En définissant "pure" à true, Angular utilisera le mécanisme de memoization pour éviter de réexécuter le pipe si les entrées n'ont pas changées.

le mécanisme de memoization ?

Le mécanisme de memoization est une technique utilisée pour améliorer les performances d'un programme en mémorisant les résultats de calculs coûteux (comme les calculs mathématiques) pour éviter de les recalculer à chaque fois qu'ils sont nécessaires.

En termes simples, cela signifie que lorsque le programme a besoin d'un résultat, il va d'abord vérifier s'il l'a déjà calculé auparavant. S'il l'a fait, il utilise simplement le résultat mémorisé à la place de recalculer. Cela permet de gagner du temps et de l'énergie en évitant de recalculer les mêmes choses plusieurs fois.

Dans le cas de Angular, il utilise memoization dans les pipes pure pour éviter de réexécuter le pipe si les entrées n'ont pas changées. Il mémorise les résultats de chaque entrée unique (input) et le retourne si elle est utilisée à nouveau. Ainsi, si vous passez les mêmes entrées à un pipe pure, Angular utilisera simplement le résultat mémorisé au lieu de réexécuter le pipe.

Mais si vous voulez que le pipe soit exécuté à chaque fois que les entrées changent, même si les entrées ont la même valeur, vous pouvez définir pure à false :

ts
@Pipe({
    name: 'extFilter',
    pure: false
})
@Pipe({
    name: 'extFilter',
    pure: false
})

WARNING

Notez que lorsque pure est false, cela signifie que le pipe sera réexécuté à chaque fois que le framework Angular détecte un changement dans l'application, même s'il n'est pas utilisé dans la vue actuelle. Cela peut entraîner des performances moins bonnes si vous utilisez de nombreux pipes avec pure à false.

Une solution plus optimisée !

N'utilisez pas pure dans la situation plus haute. Il faut changer la propriété users pour relancer le pipe. Dans ce cas, utilisez this.users = [...this.users, userCreated] pour mettre à jour l'affichage du pipe, vous devez l'utiliser dans la fonction de callback de la méthode subscribe() de l'observable renvoyé par votre service userService.create() :

ts
createUser() {
    this.userService.create().subscribe((userCreated: User) => {
        this.users = [...this.users, userCreated]
    })
}
createUser() {
    this.userService.create().subscribe((userCreated: User) => {
        this.users = [...this.users, userCreated]
    })
}

Cela remplacera l'objet this.users avec un nouvel array qui contient les éléments de l'ancien array et le nouvel utilisateur. Cela permet à Angular de détecter que les données ont changées et réexécuter le pipe pour filtrer les utilisateurs en fonction de la propriété extSelected.

this.users = [...this.users, userCreated] est une technique appelé "spread operator" qui permet de créer une nouvelle copie de l'array existant en y ajoutant des éléments. Cela est important pour éviter de muter directement l'array existant et de causer des bugs dans l'application.

spread operator ?

Le spread operator en javascript est un moyen de "décomposer" un tableau ou un objet en ses éléments individuels. Il est utilisé en utilisant les trois points (...) avant le nom du tableau ou de l'objet.

Par exemple, si vous avez un tableau appelé "nombres" contenant les nombres 1, 2 et 3, vous pouvez utiliser le spread operator pour créer un nouveau tableau avec ces nombres en utilisant la syntaxe suivante:

let nouveauTableau = [...nombres];
let nouveauTableau = [...nombres];

Cela créera un nouveau tableau appelé "nouveauTableau" qui contiendra les mêmes nombres que le tableau "nombres".

Il peut également être utilisé pour combiner des tableaux ou pour ajouter des éléments à un tableau existant.

let tableau1 = [1, 2, 3];
let tableau2 = [4, 5, 6];
let combinaisonTableaux = [...tableau1, ...tableau2];
let tableau1 = [1, 2, 3];
let tableau2 = [4, 5, 6];
let combinaisonTableaux = [...tableau1, ...tableau2];

Avec cette syntaxe, la variable "combinaisonTableaux" contiendra les éléments [1, 2, 3, 4, 5, 6].