Pipe
Cosa sono le Pipe
Le pipe sono funzioni di trasformazione utilizzate nei template Angular per formattare dati prima di visualizzarli. Sono indicate dal simbolo | (pipe) e possono essere concatenate.
<!-- Sintassi base -->
{{ valore | nomePipe }}
<!-- Con argomenti -->
{{ valore | nomePipe:argomento1:argomento2 }}
<!-- Pipe concatenate -->
{{ valore | pipe1 | pipe2 }}
Pipe Built-in
Angular fornisce numerose pipe pronte all’uso per le trasformazioni più comuni.
DatePipe
La pipe date formatta oggetti Date in stringhe leggibili.
import { Component } from '@angular/core';
import { DatePipe } from '@angular/common';
@Component({
selector: 'app-esempio-date',
standalone: true,
imports: [DatePipe],
template: `
<h2>Formattazione Date</h2>
<!-- Formati predefiniti -->
<p>Short: {{ oggi | date:'short' }}</p>
<p>Medium: {{ oggi | date:'medium' }}</p>
<p>Long: {{ oggi | date:'long' }}</p>
<p>Full: {{ oggi | date:'full' }}</p>
<!-- Solo data -->
<p>shortDate: {{ oggi | date:'shortDate' }}</p>
<p>mediumDate: {{ oggi | date:'mediumDate' }}</p>
<p>longDate: {{ oggi | date:'longDate' }}</p>
<!-- Solo ora -->
<p>shortTime: {{ oggi | date:'shortTime' }}</p>
<p>mediumTime: {{ oggi | date:'mediumTime' }}</p>
<!-- Formato personalizzato -->
<p>Custom: {{ oggi | date:'dd/MM/yyyy' }}</p>
<p>Custom: {{ oggi | date:'EEEE d MMMM yyyy, HH:mm' }}</p>
<p>Custom: {{ oggi | date:'dd-MM-yy HH:mm:ss' }}</p>
<!-- Con locale -->
<p>Italiano: {{ oggi | date:'fullDate':'':'it' }}</p>
`
})
export class EsempioDateComponent {
oggi = new Date();
}
CurrencyPipe
La pipe currency formatta numeri come valute.
import { Component } from '@angular/core';
import { CurrencyPipe } from '@angular/common';
@Component({
selector: 'app-esempio-currency',
standalone: true,
imports: [CurrencyPipe],
template: `
<!-- Default (USD) -->
<p>{{ prezzo | currency }}</p>
<!-- Euro -->
<p>{{ prezzo | currency:'EUR' }}</p>
<!-- Con simbolo -->
<p>{{ prezzo | currency:'EUR':'symbol' }}</p>
<!-- Con codice -->
<p>{{ prezzo | currency:'EUR':'code' }}</p>
<!-- Con formato decimali (minIntDigits.minFracDigits-maxFracDigits) -->
<p>{{ prezzo | currency:'EUR':'symbol':'1.2-2' }}</p>
<!-- Sterlina -->
<p>{{ prezzo | currency:'GBP' }}</p>
<!-- Yen senza decimali -->
<p>{{ prezzoYen | currency:'JPY':'symbol':'1.0-0' }}</p>
`
})
export class EsempioCurrencyComponent {
prezzo = 1299.99;
prezzoYen = 150000;
}
Pipe per Stringhe
import { Component } from '@angular/core';
import { UpperCasePipe, LowerCasePipe, TitleCasePipe, SlicePipe } from '@angular/common';
@Component({
selector: 'app-esempio-stringhe',
standalone: true,
imports: [UpperCasePipe, LowerCasePipe, TitleCasePipe, SlicePipe],
template: `
<p>Originale: {{ testo }}</p>
<p>Maiuscolo: {{ testo | uppercase }}</p>
<p>Minuscolo: {{ testo | lowercase }}</p>
<p>Title Case: {{ testo | titlecase }}</p>
<!-- Slice (sottostringa) -->
<p>Primi 5 caratteri: {{ testo | slice:0:5 }}</p>
<p>Da posizione 6: {{ testo | slice:6 }}</p>
<p>Ultimi 6 caratteri: {{ testo | slice:-6 }}</p>
`
})
export class EsempioStringheComponent {
testo = 'ciao mondo angular';
}
DecimalPipe e PercentPipe
import { Component } from '@angular/core';
import { DecimalPipe, PercentPipe } from '@angular/common';
@Component({
selector: 'app-esempio-numeri',
standalone: true,
imports: [DecimalPipe, PercentPipe],
template: `
<!-- DecimalPipe: formato 'minIntDigits.minFracDigits-maxFracDigits' -->
<p>{{ numero | number }}</p>
<p>{{ numero | number:'3.2-5' }}</p>
<p>{{ numeroPiccolo | number:'1.0-3' }}</p>
<!-- Con separatore locale italiano -->
<p>{{ numeroGrande | number:'1.2-2':'it' }}</p>
<!-- PercentPipe -->
<p>{{ percentuale | percent }}</p>
<p>{{ percentuale | percent:'2.2-2' }}</p>
<!-- Percentuali pratiche -->
<p>Progresso: {{ 0.756 | percent:'1.0-0' }}</p>
<p>Sconto: {{ 0.15 | percent }}</p>
`
})
export class EsempioNumeriComponent {
numero = 3.14159;
numeroPiccolo = 0.123456;
numeroGrande = 1234567.89;
percentuale = 0.259;
}
JsonPipe e KeyValuePipe
import { Component } from '@angular/core';
import { JsonPipe, KeyValuePipe } from '@angular/common';
@Component({
selector: 'app-esempio-json',
standalone: true,
imports: [JsonPipe, KeyValuePipe],
template: `
<!-- JsonPipe: utile per il debug -->
<pre>{{ utente | json }}</pre>
<!-- KeyValuePipe: iterare su oggetti -->
<dl>
@for (voce of configurazione | keyvalue; track voce.key) {
<dt>{{ voce.key }}</dt>
<dd>{{ voce.value }}</dd>
}
</dl>
`
})
export class EsempioJsonComponent {
utente = {
nome: 'Marco',
cognome: 'Bianchi',
eta: 30,
ruolo: 'Sviluppatore'
};
configurazione: Record<string, string | number> = {
tema: 'scuro',
lingua: 'italiano',
timeout: 3000,
versione: '2.1.0'
};
}
Pipe Personalizzate
Creare una Pipe Custom
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'tronca',
standalone: true
})
export class TroncaPipe implements PipeTransform {
transform(valore: string, lunghezzaMax: number = 50, suffisso: string = '...'): string {
if (!valore) return '';
if (valore.length <= lunghezzaMax) return valore;
return valore.substring(0, lunghezzaMax).trim() + suffisso;
}
}
Utilizzo:
<p>{{ descrizione | tronca }}</p>
<p>{{ descrizione | tronca:100 }}</p>
<p>{{ descrizione | tronca:30:' [...]' }}</p>
Pipe per il Tempo Relativo
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'tempoFa',
standalone: true
})
export class TempoFaPipe implements PipeTransform {
transform(valore: Date | string): string {
const data = new Date(valore);
const ora = new Date();
const differenzaMs = ora.getTime() - data.getTime();
const secondi = Math.floor(differenzaMs / 1000);
const minuti = Math.floor(secondi / 60);
const ore = Math.floor(minuti / 60);
const giorni = Math.floor(ore / 24);
const mesi = Math.floor(giorni / 30);
const anni = Math.floor(giorni / 365);
if (secondi < 60) return 'pochi secondi fa';
if (minuti < 60) return `${minuti} minut${minuti === 1 ? 'o' : 'i'} fa`;
if (ore < 24) return `${ore} or${ore === 1 ? 'a' : 'e'} fa`;
if (giorni < 30) return `${giorni} giorn${giorni === 1 ? 'o' : 'i'} fa`;
if (mesi < 12) return `${mesi} mes${mesi === 1 ? 'e' : 'i'} fa`;
return `${anni} ann${anni === 1 ? 'o' : 'i'} fa`;
}
}
<p>Pubblicato {{ dataArticolo | tempoFa }}</p>
<!-- Output: "Pubblicato 3 ore fa" -->
Pipe di Filtro
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'filtra',
standalone: true
})
export class FiltraPipe implements PipeTransform {
transform<T>(items: T[], campo: keyof T, valore: string): T[] {
if (!items || !valore) return items;
const termineMinuscolo = valore.toLowerCase();
return items.filter(item => {
const valCampo = String(item[campo]).toLowerCase();
return valCampo.includes(termineMinuscolo);
});
}
}
<input [(ngModel)]="termineRicerca" placeholder="Cerca...">
@for (prodotto of prodotti | filtra:'nome':termineRicerca; track prodotto.id) {
<div>{{ prodotto.nome }} - {{ prodotto.prezzo | currency:'EUR' }}</div>
}
Pipe Pure vs Impure
Pipe Pure (default)
Le pipe pure vengono eseguite solo quando Angular rileva un cambiamento nel valore di input o nei suoi argomenti. Per gli oggetti e gli array, viene controllata solo la referenza (non il contenuto).
@Pipe({
name: 'filtraPure',
standalone: true,
pure: true // default
})
export class FiltraPurePipe implements PipeTransform {
transform(items: any[], filtro: string): any[] {
console.log('Pipe pure eseguita'); // Chiamata raramente
return items.filter(i => i.nome.includes(filtro));
}
}
Pipe Impure
Le pipe impure vengono eseguite ad ogni ciclo di change detection, anche se l’input non è cambiato. Usale con cautela per ragioni di performance.
@Pipe({
name: 'filtraImpure',
standalone: true,
pure: false // Eseguita ad ogni change detection
})
export class FiltraImpurePipe implements PipeTransform {
transform(items: any[], filtro: string): any[] {
console.log('Pipe impure eseguita'); // Chiamata frequentemente
return items.filter(i => i.nome.includes(filtro));
}
}
Quando usare pipe impure:
- Quando la pipe dipende da stato esterno (es. un servizio)
- Quando l’input e un array/oggetto mutato internamente senza cambiare referenza
Soluzione alternativa alle pipe impure
Invece di usare pipe impure, crea un nuovo array quando i dati cambiano:
// Nel componente, crea un nuovo riferimento quando muti l'array
aggiungiProdotto(prodotto: Prodotto): void {
this.prodotti = [...this.prodotti, prodotto]; // Nuovo riferimento
// NON: this.prodotti.push(prodotto); // Stesso riferimento, pipe pure non si attiva
}
Async Pipe
La async pipe si sottoscrive automaticamente a un Observable o Promise e restituisce l’ultimo valore emesso. Gestisce automaticamente la cancellazione della sottoscrizione quando il componente viene distrutto.
import { Component, OnInit } from '@angular/core';
import { AsyncPipe, DatePipe, CurrencyPipe } from '@angular/common';
import { Observable, interval, map } from 'rxjs';
import { HttpClient } from '@angular/common/http';
interface Utente {
id: number;
nome: string;
email: string;
}
@Component({
selector: 'app-esempio-async',
standalone: true,
imports: [AsyncPipe, DatePipe, CurrencyPipe],
template: `
<!-- Con Observable -->
@if (utenti$ | async; as utenti) {
@for (utente of utenti; track utente.id) {
<div>{{ utente.nome }} - {{ utente.email }}</div>
}
} @else {
<p>Caricamento utenti...</p>
}
<!-- Timer con async pipe -->
<p>Secondi trascorsi: {{ timer$ | async }}</p>
<!-- Async pipe con altre pipe -->
<p>Orario: {{ oraCorrente$ | async | date:'HH:mm:ss' }}</p>
<!-- Con Promise -->
<p>Messaggio: {{ messaggioPromise | async }}</p>
`
})
export class EsempioAsyncComponent implements OnInit {
utenti$!: Observable<Utente[]>;
timer$!: Observable<number>;
oraCorrente$!: Observable<Date>;
messaggioPromise!: Promise<string>;
constructor(private http: HttpClient) {}
ngOnInit(): void {
// Observable da HTTP
this.utenti$ = this.http.get<Utente[]>('/api/utenti');
// Timer che emette ogni secondo
this.timer$ = interval(1000);
// Ora corrente aggiornata ogni secondo
this.oraCorrente$ = interval(1000).pipe(
map(() => new Date())
);
// Promise
this.messaggioPromise = new Promise(resolve => {
setTimeout(() => resolve('Dati caricati!'), 2000);
});
}
}
Vantaggi dell’async pipe
L’async pipe è la soluzione raccomandata perche:
- Gestisce automaticamente la sottoscrizione e la cancellazione
- Previene memory leak senza bisogno di
ngOnDestroy - Funziona con OnPush change detection strategy
- Riduce il codice boilerplate nel componente
// SENZA async pipe (più codice, rischio memory leak)
export class SenzaAsyncComponent implements OnInit, OnDestroy {
utenti: Utente[] = [];
private sub!: Subscription;
ngOnInit(): void {
this.sub = this.http.get<Utente[]>('/api/utenti')
.subscribe(utenti => this.utenti = utenti);
}
ngOnDestroy(): void {
this.sub.unsubscribe(); // Necessario!
}
}
// CON async pipe (più pulito, nessun memory leak)
export class ConAsyncComponent {
utenti$ = this.http.get<Utente[]>('/api/utenti');
constructor(private http: HttpClient) {}
}
Best Practices
- Preferisci le pipe pure per le prestazioni; usa pipe impure solo quando strettamente necessario
- Usa la async pipe per gestire Observable nei template e evitare memory leak
- Le pipe devono essere funzioni pure senza effetti collaterali
- Crea pipe personalizzate per formattazioni riutilizzabili nell’applicazione
- Non usare pipe di filtro o ordinamento su liste grandi: sposta la logica nel componente
- Concatena le pipe quando serve:
{{ data | date:'short' | uppercase }} - Testa le pipe personalizzate con test unitari: sono semplici da testare poiche sono funzioni pure