back to top

Le pipe in Angular

Proseguiamo anche in questa lezione ad esplorare ulteriori funzionalità presenti in Angular per lavorare con i template. In particolare andiamo ad approfondire un argomento con cui abbiamo iniziato a prendere familiarità in alcuni degli esempi visti finora. Parleremo infatti delle Pipe e vedremo cosa sono, come utilizzare quelle già presenti in Angular e come crearne delle nuove.

Cosa sono le Pipe in Angular e come usarle

Le Pipe consentono di trasformare e formattare delle informazioni prima di essere mostrate all’interno di un template. Vedremo a breve come sia semplice creare una pipe personalizzata in Angular, ma possiamo dire fin da ora che dovremo sostanzialmente indicare in che modo i dati ricevuti in ingresso dalla pipe devono essere elaborati per poi restituire un nuovo valore che viene inserito all’interno del template di un componente.

Abbiamo già visto come usare una pipe. Per esempio nella precedente lezione abbiamo illustrato come utilizzare la pipe titlecase.

<li>{{ "pomodoro san marzano" | titlecase }}</li>

All’interno dell’espressione di interpolazione abbiamo una stringa che, attraverso l’operatore pipe (|), viene passata in ingresso alla pipe titlecase la quale modifica la stringa rendendo tutte le lettere iniziali maiuscole. Otteniamo quindi il seguente risultato:

<li>Pomodoro San Marzano</li>

La sintassi è uguale per tutte le pipe. Nella forma più semplice si ha un valore in ingresso che viene passato alla pipe attraverso l’operatore pipe (|). La pipe modifica opportunamente i dati in ingresso e restituisce un valore nel nuovo formato.

Parametri di una Pipe

Una pipe può ricevere uno o più parametri che consentono di personalizzare il suo comportamento. Per specificare un parametro basta aggiungere il carattere due punti (:) dopo il nome della pipe, seguito dal valore del parametro stesso.

<!-- output: '€123.00' -->
<p>{{ 123 | currency:'EUR' }}</p>

Se la pipe può ricevere più di un parametro, basterà separare i valori con i due punti.

<!-- output: 'GBP345.00' -->
<p>{{ 345 | currency:'GBP':'code' }}</p>

Pipe predefinite

Angular mette a disposizione una serie di pipe predefinite che possiamo usare, come visto nei precedenti esempio, senza doverle importare esplicitamente. Riportiamo di seguito alcune delle pipe più utili e vediamo come utilizzarle attraverso degli esempi. Per maggiori dettagli e per una lista completa dei parametri accettati, è consigliato consultare la documentazione ufficiale.

CurrencyPipe

Già vista e utilizzata in un paio di esempi di questa guida, questa pipe può essere impiegata per la formattazione di valori numerici che rappresentano delle somme di denaro. Il primo parametro rappresenta la valuta da usare. Se omessa, Angular ripiega sul valore predefinito ‘USD’. Il secondo parametro permette di personalizzare il simbolo della valuta che precede la somma di denaro. Nell’esempio precedente abbiamo espresso la volontà di usare il codice identificativo di tre cifre della valuta al posto del simbolo £.

<!-- output: '£1,000.00' -->
<li>{{ 1000 | currency:'GBP':'symbol':'4.2-2' }}</li>

Nell’esempio riportato sopra abbiamo indicato un terzo parametro che esprime come deve essere formatato il valore numerico. Tale parametro è nella forma ‘{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}’:

  • minintegerdigits: il numero minimo di cifre intere prima del punto che separa le cifre decimali. Il valore predefinito è 1.
  • minfractiondigits: il numero minimo di cifre decimali. Valore predefinito è 0.
  • maxFractionDigits: numero massimo di cifre decimali. Valore predefinito 3.

DecimalPipe

La DecimalPipe trasforma un numero in una stringa formattata secondo le regole espresse dal primo parametro il quale, anche in questo caso, segue lo schema ‘{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}’ che ha lo stesso significato e i valori predefiniti appena esposti per la pipe precedente. È possibile specificare anche un secondo parametro che serve ad indicare se si vogliono usare le regole di formattazione e punteggiatura relative ad un determinato paese.

<!-- output: 08.40 -->
<p>{{ 8.4 | number: '2.2-4' }}</p>
<!-- output: 3.14 -->
<p>{{ 3.14159265359 | number: '1.2-2' }}</p>

PercentPipe

Trasforma un numero in una stringa percentuale e accetta gli stessi parametri di DecimalPipe.

<!-- output: 003.00% -->
<p>{{ 0.03 | percent: '3.2-4' }}</p>
<!-- output: 84.0000% -->
<p>{{ 0.84 | percent: '1.4-6' }}</p>

LowerCasePipe, UpperCasePipe e TitleCasePipe

Queste tre pipe permettono di trasformare la rappresentazione di una stringa convertendo ogni lettera in caratteri minuscoli, nel caso di LowerCasePipe, e in caratteri maiuscoli, nel caso di UpperCasePipe. La pipe TitleCasePipe fa in modo che le lettere iniziali di ciascuna parola di una stringa siano maiuscole.

<!-- output: 'penne alla norcina' -->
<p>{{ 'Penne Alla Norcina' | lowercase }}</p>
<!-- output: 'BISTECCA ALLA FIORENTINA' -->
<p>{{ 'Bistecca alla fiorentina' | uppercase }}</p>
<!-- output: 'Baci Di Dama' -->
<p>{{ 'baci di dama' | titlecase }}</p>

SlicePipe

Crea un nuovo array o stringa contenente un sottoinsieme degli elementi presenti nell’originale. Accetta due parametri inizio e fine, entrambi di tipo intero.

{{ espressione | slice : inizio [ : fine ] }}

Il parametro inizio indica l’indice a partire dal quale viene ricavato il sottoinsieme. Può assumere i seguenti valori:

  1. un valore intero positivo che indica l’indice dell’elemento, a partire da zero, da cui iniziare la selezione del sottoinsieme cominciando dall’inizio dell’array o della stringa. Nel caso tale valore fosse superiore alla dimensione della stringa o dell’array iniziale, verrebbe restituito un array o una stringa vuoti.
  2. un valore intero negativo che indica l’indice da cui iniziare la selezione del sottoinsieme partendo dalla fine dell’array o della stringa. Se questo valore è maggiore della dimensione della stringa o dell’array iniziale e viene omesso il parametro fine, viene restituita l’intera stringa.

Il parametro fine rappresenta l’indice dell’elemento fino al quale deve essere ritagliato il nuovo sottoinsieme. Il suo valore di default è undefined e in questo caso vengono restituiti tutti gli elementi fino alla fine della stringa o dell’array. Però può anche essere:

  1. un intero positivo che indica l’indice dell’elemento prima del quale bisogna interrompere la selezione del nuovo sottoinsieme.
  2. un intero negativo che indica l’indice, contando a ritroso dalla fine dell’array o della stringa, prima del quale bisogna interrompere la selezione del nuovo sottoinsieme.

Vediamo quindi alcuni esempi per chiarire meglio il funzionamento della SlicePipe:

esempi SlicePipe Angular

DatePipe

DatePipe serve per rappresentare un oggetto Javascript di tipo Date sotto forma di stringa sulla base dei parametri che vengono passati. Il primo di questi parametri è opzionale ed è una stringa che indica quale formato usare. Sono disponibili un gran numero di opzioni che sarebbe complicato riportare in questa lezione, ma che sono consultabili sulla documentazion ufficiale. Vediamo ora alcuni esempi per capire come usare questa pipe.

<!-- Output: Oct 6, 2018-->
<p>{{ today | date }}</p>

<!-- Output: Saturday, October 6, 2018-->
<p>{{ today | date:'fullDate' }}</p>

<!-- Output: Oct 6, 2018-->
<p>{{ today | date:'mediumTime' }}</p>

<!-- Output: 06/10/2018-->
<p>{{ today | date:'dd/MM/yyyy' }}</p>

JsonPipe

Converte un oggetto Javascript nella sua rappresentazione in formato JSON.

<!-- { "a": 1, "b": 2, "c": [ 1, 2, 3 ], "d": { "e": "a", "f": [ "b", "c", "d" ] } } -->
<div>{{ obj | json }}</div>

AsyncPipe

Questa pipe accetta un Observable o una Promise e permette di inserire nel template il loro output senza dover invocare i metodi subscribe() (per gli Observable) o .then() (per le promise). Parleremo degli Observable in una delle prossime lezioni. Per ora illustriamo un semplice esempio che fa uso di una Promise.

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

@Component({
  selector: 'simple-root',
  template: `
    <p [hidden]="!loading">Loading...</p>
    <p [hidden]="loading">{{ promise | async | json }}</p>
  `
})
export class AppComponent implements OnInit {
  user: Object = {id: 26, name: 'Sofia', surname: 'Verdi'};
  promise: Promise<Object>;
  loading = true;

  ngOnInit() {
    this.promise = this.getUser();
  }

  getUser(): Promise<Object> {
    return new Promise((resolve, reject) => {
      setTimeout(() => { this.loading = false; resolve(this.user); }, 2000);
    });
  }
}

Nel frammento di codice riportato sopra, abbiamo definito un metodo getUser(), invocato in fase di inizializzazione del componente, il quale restituisce una promise che viene risolta dopo 2 secondi. Attraverso l’uso della pipe async accediamo, direttamente nel template, al valore restituito dalla promise. Dal momento che si tratta di un oggetto, sfruttiamo anche la pipe json per mostrarlo all’interno della pagina HTML.

esempio angular async pipe

Concatenare due o più pipe

Possiamo concatenare due o più pipe attraverso il carattere pipe (|) che consente di passare il risultato di una pipe alla successiva. Ovviamente è possibile utilizzare eventuali parametri come mostrato nell’esempio sottostante.

<!-- output: 'San Marzano' -->
<li>{{ 'pomodoro san marzano' | slice:9 | titlecase }}</li>

Come creare una pipe personalizzata in Angular

Nonostante le pipe messe a disposizione da Angular sono davvero numerose, in alcuni casi è necessario crearne una adatta alle nostre esigenze. Definire una pipe è estremamente facile e i passaggi da seguire sono simili a quelli visti per i componenti.

Una Pipe è infatti una classe a cui viene applicato il decoratore @Pipe che consente di specificare il nome da usare per la pipe all’interno di un template.

Possiamo creare una pipe manualmente o attraverso Angular CLI che esegue tutti i passaggi di configurazione iniziale al nostro posto.

ng generate pipe dashCase --no-spec
CREATE src/app/dash-case.pipe.ts (205 bytes)
UPDATE src/app/app.module.ts (381 bytes)

Ovviamente Angular CLI si occupa di inserire la nuova pipe all’interno dell’array ‘declarations’ del modulo AppModule.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';
import { DashCasePipe } from './dash-case.pipe';

@NgModule({
  declarations: [
    AppComponent,
    // Angular CLI aggiunge la pipe all'array 'declarations'
    DashCasePipe
  ],
  imports: [
    BrowserModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Viene quindi creato un file dash-case.pipe.ts contenente il codice iniziale da cui partire per implementare le funzionalità della nuova pipe.

// file iniziale dash-case.pipe.ts generato da Angular CLI
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'dashCase'
})
export class DashCasePipe implements PipeTransform {

  transform(value: any, args?: any): any {
    return null;
  }

}

Prima di passare all’implementazione della pipe DashCasePipe, soffermiamoci per un momento a commentare il frammento di codice riportato sopra.

Notiamo che per definire una pipe dobbiamo creare una nuova classe a cui va applicato il decoratore @Pipe che permette di specificare il nome della pipe che sarà usato all’interno di un template. La nostra classe dovrà implementare l’interfaccia PipeTransform che presenta il solo metodo transform() il quale accetta un valore in ingresso e un numero non definito di parametri e restituisce il valore elaborato che sarà poi inserito nel template in cui viene utilizzata la pipe. Sia il decoratore @Pipe che l’interfaccia PipeTransform sono importati da @angular/core.

Dopo aver analizzato il codice della classe generata da Angular CLI, passiamo all’implementazione. Creiamo una pipe basilare che prende in ingresso una stringa che usa la notazione CamelCase e la trasforma in una stringa che segue la notazione dash-case. La pipe accetta anche un parametro opzionale di tipo boolean (valore predefinito false) per invertire eventualmente la stringa.

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'dashCase'
})
export class DashCasePipe implements PipeTransform {

  transform(value: string, reverse = false): string {
    if (reverse) {
      value = this.reverse(value);
    }
    return value.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  private reverse(str: string): string {
    return (str === '') ? '' : this.reverse(str.substr(1)) + str.charAt(0);
  }
}

Oltre al metodo transform, abbiamo creato un metodo privato reverse che ci aiuta a invertire ricorsivamente la stringa ricevuta in ingresso. Utilizziamo tale metodo solo nelle situazioni in cui venga passato un argomento opzionale reverse pari a true. In ogni caso convertiamo la stringa ricevuta in ingresso dal formato CamelCase a dash-case usando il metodo String.prototype.replace() a cui passiamo come primo argomento un’espressione regolare. Tale metodo cerca all’interno della stringa tutte le coppie di caratteri in cui una lettera minuscola è immediatamente seguita da una maiuscola e le sostituisce con la stringa passata come secondo argomento. Nell’espressione regolare abbiamo usato due gruppi di cattura identificati dalle parentesi tonde in modo tale da poter accedere a ciascuna lettera da essi individuata nella stringa di sostituzione attraverso i pattern di sostituzione speciali $1 e $2. Per esempio se la nostra stringa è ‘PascalCase’, il metodo String.prototype.replace() restituisce ‘Pascal-Case’. Il risultato così ottenuto viene convertito in caratteri minuscoli grazie a String.prototype.toLowerCase().

rappresentazione grafica string replace con uso di espressioni regolari

Possiamo quindi utilizzare la pipe appena creata all’interno del template di un qualsiasi componente disponibile nel modulo corrente.

<!-- output: 'pascal-case' -->
<p>{{ 'PascalCase' | dashCase }}</p>

<!-- output: 'esa-clacsa-p' -->
<p>{{ 'PascalCase' | dashCase : true }}</p>

Pipe pure e impure

Nell’esempio appena visto abbiamo creato una pipe pura. Infatti tutte le pipe che vengono create sono pure a meno che non si indichi il contrario passando al decoratore @Pipe un oggetto con la proprietà pure: false.

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

Le pipe pure vengono eseguite solo quando si verifica una variazione pura del valore che la pipe riceve in ingresso. Ciò significa che se l’input è di tipo primitivo (String, Number, Boolean, Symbol), la pipe viene invocata ogni volta che il suo valore cambia. Se l’input contiene invece un riferimento a un oggetto, a un array o a una funzione, la pipe viene invocata solo se tale riferimento cambia e non se viene effettuata una modifica o un aggiornamento dei valori. Per esempio, se passiamo in ingresso a una pipe pura una proprietà che contiene un riferimento a un array, la pipe viene invocata solo se viene modificato il riferimento della proprietà, ovvero se alla proprietà viene assegnato un nuovo array. Se invece aggiungiamo o rimuoviamo un valore dall’array già esistente, la pipe pura non viene invocata nuovamente. Tale scelta è stata presa per migliorare le prestazioni delle applicazioni e limitare il numero e la durata dei controlli necessari su strutture dati più complesse.

Al contrario una pipe impura viene eseguita ad ogni ciclo di change detection del componente. Se per esempio una pipe impura riceve in ingresso un array, ogni inserimento o rimozione di un elemento avvia l’esecuzione della pipe. Ciò significa che il lavoro necessario per le ripetute esecuzioni di una pipe di questo tipo può comportare un carico sostanzioso con il conseguente degrado delle prestazioni di un’applicazione.

In base a ciò che abbiamo appena affermato, è consigliabile usare una pipe di tipo puro per quanto possibile o cercare di capire se si può implementare una certa funzionalità usando direttamente una proprietà di un componente invece di utilizzare una pipe.

Riepilogo

In questa lezione abbiamo illustrato come utilizzare le pipe presenti in Angular per arricchire e formattare al meglio le informazioni da mostrare nel template di un componente. Abbiamo visto che è possibile passare degli argomenti o concatenare due o più pipe. Abbiamo infine imparato a creare una pipe personalizzata attraverso un esempio. Nella prossima lezione torneremo a rivolgere la nostra attenzione ai componenti e parleremo del loro ciclo di vita.

Pubblicitร