Les Pipes sont des filtres utilisables directement depuis la vue afin de transformer les valeurs lors du "binding".
La syntaxe des Pipes est simplement inspirée des Pipes des shell UNIX que l'on retrouve dans de nombreux systèmes de templating.
<div>{{ user.firstName | lowercase }}</div>
Les Pipes peuvent prendre des paramètres qu'il faut mettre après le Pipe et séparés avec le symbole ":".
<div>{{ user.firstName | slice:0:10 }}</div>
Les "pipes" peuvent être chaînés.
<div>{{ user.firstName | slice:0:10 | lowercase }}</div>
Angular dispose de plusieurs "pipes" natifs : https://angular.io/api?type=pipe.
Le Pipe async est un Pipe capable de consommer des Observables (ou Promise) en appelant implicitement la méthode subscribe (ou then) afin de "binder" les valeurs contenus dans l'Observable (ou la Promise).
Dans les cas des Observables, ce Pipe unsubscribe automatiquement à la destruction de la vue.
Cf. Gestion de la Subscription.
Pour créer un Pipe personnalisé, il faut :
- implémenter une classe suivant l'interface
PipeTransform, - décorer cette classe avec le décorateur
@Pipe()en indiquant le nom duPipe. - ajouter la classe aux
declarations(etexports) du module associé.
Le Pipe price ci-dessous permet d'afficher le prix d'un produit représenté avec la classe Price suivante :
{% tabs %} {% tab title="price.ts" %}
export class Price {
coefficient?: number;
exponent?: number;
currency?: string;
constructor(args: Price = {}) {
this.coefficient = args.coefficient;
this.exponent = args.exponent;
this.currency = args.currency;
}
}{% endtab %} {% endtabs %}
{% tabs %} {% tab title="price.pipe.ts" %}
@Pipe({
name: 'price'
})
export class PricePipe implements PipeTransform {
transform(price: Price): string {
if (price == null) {
return null;
}
const amount = price.coefficient * Math.pow(10, price.exponent);
return `${amount} ${price.currency}`;
}
}{% endtab %} {% endtabs %}
{% tabs %} {% tab title="price.module.ts" %}
@NgModule({
declarations: [
PricePipe
],
exports: [
PricePipe
]
})
export class PriceModule {
}{% endtab %} {% endtabs %}
Le Pipe peut alors être utilisé dans n'importe quel composant contenu dans un module qui importe le module PriceModule :
{% tabs %} {% tab title="book-preview.component.ts" %}
export class BookPreviewComponent {
bookPrice = new Price({
coefficient: 1010,
exponent: -2,
currency: 'USD'
});
}{% endtab %} {% endtabs %}
{% tabs %} {% tab title="book-preview.component.html" %}
<div>{{ bookPrice | price }}</div>
{% endtab %} {% endtabs %}
On obtient alors le résultat suivant :
<div>10.1 USD</div>
Le constructeur d'un Pipe peut être utiliser pour injecter des dépendances.
Dans notre cas, nous souhaitons injecter le Pipe currency afin de profiter de ses fonctionnalités tout en simplifiant son utilisation.
{% tabs %} {% tab title="price.pipe.ts" %}
@Pipe({
name: 'price'
})
export class PricePipe implements PipeTransform {
constructor(private _currencyPipe: CurrencyPipe) {
}
transform(price: Price): string {
if (price == null) {
return null;
}
const amount = price.coefficient * Math.pow(10, price.exponent);
return this._currencyPipe.transform(amount, price.currency);
}
}{% endtab %} {% endtabs %}
{% hint style="warning" %}
Malheureusement, Angular ne définit pas nativement de providers pour les Pipes.
En attendant la résolution de ce bug angular/angular#15107, une des solutions de contournement pour injecter un Pipe est de l'ajouter à la liste des providers d'un module importé par le module définissant notre Pipe.
/* @HACK: Cf. https://github.com/angular/angular/issues/15107 */
@NgModule({
providers: [
CurrencyPipe
]
})
export class InjetableCurrencyPipeModule {
}
@NgModule({
imports: [
InjetableCurrencyPipeModule
]
})
export class BookModule {
}{% endhint %}
Par défaut, les Pipes sont purs. C'est à dire que leur méthode transform est une fonction pure, la valeur retournée ne dépend que des paramètres reçus et les appels n'ont aucun effet de bord. Autrement dit, le Pipe est "stateless".
{% hint style="warning" %}
Par optimisation, à chaque Change Detection, Angular n'évalue les Pipes purs (i.e. appel de la méthode transform) que quand les paramètres changent.
Angular ne considère que les paramètres ont changé que si leurs références ont changé.
Il faut donc veiller au respect de l'immutabilité. {% endhint %}
Les Pipes impurs sont évalués à chaque Change Detection même quand les paramètres ne changent pas.
Ils sont déclarés en passant la valeur false au paramètre pure du décorateur Pipe :
@Pipe({
pure: false
})
export class BadPipe {
}Nativement, seuls les Pipes suivant sont impurs :
async: Il est impur car les valeurs retournées par cePipepeuvent changer à n'importe quel moment étant donné qu'elles proviennent d'une source asynchrone (ObservableouPromise).json: Etant donné qu'il est principalement utilisé pour le "debug", cePipeest impur car il est préférable que la valeur retournée soit mise à jour même si l'immutabilité n'est pas respectée.slice: Bien que cePipefonctionnerait parfaitement en étant pur, il est probablement impur pour faciliter l'adoption d'Angular par la communauté car malheureusement l'immutabilité desArrays est rarement respectée par les développeurs Angular.
{% hint style="danger" %} Ne les utilisez pas !
L'implémentation de Pipes impurs est un "code smell" qui révèle généralement des problèmes de conception.
{% endhint %}