Pour afficher une donnée sous un format spécifique, Angular vous propose les Pipes. Le framework en fournit plusieurs, comme :
DatePipe, pour les dates ;
UpperCasePipe, LowerCasePipe et TitleCasePipe pour les chaînes de caractères ;
DecimalPipe, PercentPipe et CurrencyPipe pour les nombres.
Mais saviez-vous qu'Angular vous permet d'aller plus loin, en créant vos propres Pipes ? Imaginons quelques cas de figure :
vous voulez afficher un aperçu d'un texte, avec au plus 50 caractères, suivis de … pour montrer qu'il y a une suite ;
vous recevez des objets User de votre backend qui contiennent séparément les noms et prénoms des utilisateurs, et vous avez souvent besoin d'afficher le nom complet sous la forme "NOM Prénom" ;
au lieu d'afficher une date exacte, vous voulez afficher un texte descriptif de type "il y a quelques minutes".
Dans ces trois situations, la solution du Pipe est pertinente : vous voulez afficher une donnée sous un format spécifique sans modifier la donnée sous-jacente.
Puisque les Pipes que vous créez ici pourront être utiles dans toute l'application, vous allez les déclarer dans SharedModule. Vous allez commencer par le Pipe qui affichera un aperçu d'un texte, suivi des … .
Dans le dossier shared , créez un dossier pipes , et créez-y un fichier shorten.pipe.ts :
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'shorten'
})
export class ShortenPipe implements PipeTransform {
transform(value: string): string {
if (value.length <= 50) {
return value;
}
return value.substring(0, 50) + '…';
}
}Pour créer un Pipe personnalisé :
vous utilisez le décorateur @Pipe , auquel vous passez un name qui sera le nom utilisé dans les templates pour appliquer ce Pipe ;
vous créez une classe qui implémente l'interface PipeTransform ;
pour implémenter PipeTransform , votre classe doit contenir une méthode transform() – c'est cette méthode qui prendra la donnée comme argument et la retournera transformée.
Ici, si le texte reçu fait moins de 50 caractères, vous le retournez non transformé. S'il en fait plus, vous le retournez tronqué, suivi du caractère … .
Pour utiliser un Pipe dans l'application, il faut le déclarer. Ajoutez-le aux déclarations de SharedModule, ainsi qu'aux exports pour le rendre disponible à toute l'application :
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CommentsComponent } from './components/comments/comments.component';
import { MaterialModule } from './material.module';
import { ReactiveFormsModule } from '@angular/forms';
import { ShortenPipe } from './pipes/shorten.pipe';
@NgModule({
declarations: [
CommentsComponent,
ShortenPipe
],
imports: [
CommonModule,
MaterialModule,
ReactiveFormsModule
],
exports: [
CommentsComponent,
MaterialModule,
ReactiveFormsModule,
ShortenPipe
]
})
export class SharedModule { }Vous pouvez maintenant appliquer ce Pipe sur le content des Posts. Dans post-list-item.component.html :
<span class="post-content">{{ post.content | shorten }}</span>Dans le DOM, ça donne :

Aussi simple que ça ! Mais on peut l'améliorer !
Pour rendre ShortenPipe encore plus utile, vous allez lui faire accepter un argument optionnel pour fixer le nombre maximal de caractères (plutôt que garder les 50 caractères codés en dur). Ça se fait en ajoutant un deuxième paramètre à transform :
transform(value: string, maxLength = 50): string {
if (value.length <= maxLength) {
return value;
}
return value.substring(0, maxLength) + '…';
}Cela vous permet, dans vos templates, de choisir le nombre de caractères auquel vous limitez les textes :
<span class="post-content">{{ post.content | shorten: 100 }}</span>Allez, passons à un Pipe qui accepte autre chose qu'une chaîne de caractères comme paramètre.

Je vous propose d'essayer d'implémenter vous-même ce prochain Pipe qui :
acceptera un objet de type { firstName: string, lastName: string } ;
retournera "LASTNAME Firstname" .
Par exemple, l'objet { lastName: 'Alexander', firstName: 'Will' } doit retourner 'ALEXANDER Will' . Vous pourrez essayer votre Pipe en créant un objet temporaire dans l'un de vos components actuels.
Faites l'essai, et je vous propose ma solution ci-dessous.
Voici ma proposition :
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'username'
})
export class UsernamePipe implements PipeTransform {
transform(value: { firstName: string, lastName: string }): string {
return `${value.lastName.toUpperCase()} ${value.firstName}`;
}
}Besoin de rien de plus ! Si vous déclarez ce Pipe et que vous l'utilisez dans l'un de vos templates avec un objet User temporaire, ça fonctionne tout seul !
Voici une idée pour une amélioration de UsernamePipe :
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'username'
})
export class UsernamePipe implements PipeTransform {
transform(value: { firstName: string, lastName: string }, locale: 'en' | 'fr' = 'fr'): string {
return locale === 'fr' ?
`${value.lastName.toUpperCase()} ${value.firstName}` :
`${value.firstName} ${value.lastName}`;
}
}Ici, je permets au développeur de choisir de localiser l'affichage du nom :
soit en 'fr' (qui est également la valeur par défaut), en gardant le nom en majuscules suivi du prénom ;
soit en 'en' , avec le prénom suivi du nom.
Allez, un dernier Pipe qui demande un peu plus d'une ligne de code, et qui crée un comportement que plusieurs de mes clients m'ont demandé !
L'idée ici est d'afficher un texte descriptif de type "il y a quelques minutes" à la place des dates exactes.
Dans votre application, les dates createdDate sont sous forme de string, mais pour que ce Pipe soit le plus réutilisable possible, vous allez prendre en compte les deux possibilités : string et Date .
Voici l'implémentation que je vous propose :
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'timeAgo'
})
export class TimeAgoPipe implements PipeTransform {
timeDiffs = {
minute: 60 * 1000,
hour: 60 * 60 * 1000,
day: 24 * 60 * 60 * 1000,
week: 7 * 24 * 60 * 60 * 1000,
month: 30 * 24 * 60 * 60 * 1000,
year: 365 * 24 * 60 * 60 * 1000
};
transform(value: string | Date): any {
const now = Date.now();
const then = new Date(value).getTime();
const diff = now - then;
if (diff < this.timeDiffs.minute) {
return 'Il y a quelques secondes';
} else if (diff < this.timeDiffs.hour) {
return 'Il y a quelques minutes';
} else if (diff < this.timeDiffs.day) {
return 'Il y a quelques heures';
} else if (diff < this.timeDiffs.week) {
return 'Il y a quelques jours';
} else if (diff < this.timeDiffs.month) {
return 'Il y a quelques semaines';
} else if (diff < this.timeDiffs.year) {
return 'Il y a quelques mois';
} else {
return 'Il y a plus d\'un an';
}
}
}Dans ce Pipe :
l'objet timeDiffs permet de regrouper (et de nommer) des durées en millisecondes pour la comparaison de deux dates ;
la méthode transform accepte des string ou des Date ;
une première constante now contient le moment exact du runtime sous forme du nombre de millisecondes depuis le 1er janvier 1970 (l'époque UNIX) ;
une deuxième constante then crée un nouvel objet Date à partir de value – ça marche pour les types string et Date – et utilise getTime() pour récupérer le nombre de millisecondes correspondant ;
une dernière constante diff correspond à la différence entre now et then .
On compare la valeur de diff aux différentes durées dans timeDiffs pour retourner un texte descriptif approprié.
Déclarez et exportez TimeAgoPipe dans SharedModule, et remplacez DatePipe dans PostListItemComponent ainsi que dans CommentsComponent, et vous aurez :

Implémenter ce genre de comportement serait possible sans Pipe, mais j'espère vous avoir convaincu que lorsque vous devez transformer une valeur pour l'afficher dans un template, l'utilisation d'un Pipe est généralement le meilleur choix architectural !
On déclare un Pipe avec le décorateur @Pipe en y passant le nom que l'on souhaite utiliser.
La classe du Pipe implémente l'interface PipeTransform avec une méthode transform qui accepte au moins un paramètre, et le retourne transformé.
Passer un deuxième argument à transform permet d'implémenter une configuration depuis le template ; ex. : {{ myText | shorten: 100 }} ;
Les Pipes peuvent accepter plusieurs types en paramètre, y compris des objets complexes.
Maintenant que vous savez implémenter vos propres Pipes, suivez-moi au prochain chapitre pour apprendre comment créer des comportements dynamiques avec vos propres Directives !