Angular in 10 steps - 9 of 10

09 - Interceptor e pipe: gli attrezzi di finitura
Penultimo capitolo del modulo base, e lo dedichiamo a due strumenti che non costruiscono pagine ma rendono migliore tutto il resto:
- gli interceptor, per mettere le mani su ogni chiamata HTTP da un punto solo: token, log, gestione errori
- le pipe, per trasformare i dati direttamente nel template: date, valute, testi da accorciare
Sono i classici attrezzi che, una volta scoperti, ti chiedi come facevi prima.
🚦 Interceptor: la dogana delle richieste
Un interceptor si mette in mezzo tra la tua app e il server: ogni richiesta HTTP in uscita, e ogni risposta in entrata, passa da lì. Come una dogana, può lasciar passare, modificare, registrare o respingere.
I casi d'uso reali sono sempre gli stessi quattro, e prima o poi ti serviranno tutti:
- aggiungere il token di autenticazione a ogni chiamata
- loggare richieste e tempi di risposta
- gestire gli errori in un punto centrale (401 → redirect al login)
- mostrare un loader globale quando c'è traffico in corso
Il punto è sempre lo stesso: senza interceptor, questa logica andrebbe copiata in ogni service. Con l'interceptor la scrivi una volta.
🛠 Esempio: il token su ogni richiesta
Oggi un interceptor è una semplice funzione di tipo HttpInterceptorFn:
riceve la richiesta e la funzione next per farla proseguire.
1// auth.interceptor.ts
2import { HttpInterceptorFn } from '@angular/common/http';
3
4export const authInterceptor: HttpInterceptorFn = (req, next) => {
5 const token = localStorage.getItem('token');
6
7 // le richieste HTTP sono immutabili: si clona, non si modifica
8 const reqClone = token
9 ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
10 : req;
11
12 return next(reqClone);
13};Due dettagli da notare. Il primo: le richieste sono immutabili, quindi non
si modificano ma si clonano con le differenze (req.clone(...)). Il
secondo: essendo una funzione eseguita nel contesto di Angular, dentro un
interceptor puoi usare inject(): un AuthService vero, al posto del
localStorage, si inietta esattamente come nei capitoli scorsi.
Si registra in app.config.ts, agganciandolo al provideHttpClient che
conosciamo dal capitolo 7:
1import { provideHttpClient, withInterceptors } from '@angular/common/http';
2import { authInterceptor } from './auth.interceptor';
3
4export const appConfig: ApplicationConfig = {
5 providers: [provideHttpClient(withInterceptors([authInterceptor]))],
6};Nei progetti meno recenti troverai gli interceptor come classi che implementano
HttpInterceptor, registrate con il providerHTTP_INTERCEPTORS(o conwithInterceptorsFromDi()). Stessa idea, più cerimonia: per il codice nuovo usa le funzioni.
Un secondo esempio, giusto per vedere che si può agire anche sulla risposta: un log dei tempi di ogni chiamata.
1import { HttpInterceptorFn } from '@angular/common/http';
2import { finalize } from 'rxjs';
3
4export const timingInterceptor: HttpInterceptorFn = (req, next) => {
5 const start = Date.now();
6 return next(req).pipe(
7 finalize(() => console.log(`${req.method} ${req.url}: ${Date.now() - start}ms`))
8 );
9};Gli interceptor si mettono in fila: withInterceptors([authInterceptor, timingInterceptor]) li esegue nell'ordine in cui li elenchi.
🪄 Pipe: formattare senza sporcare
Cambiamo completamente zona: dalle richieste HTTP al template. Le pipe
trasformano un valore al momento di mostrarlo, con la sintassi |:
<p>{{ oggi | date:'longDate' }}</p>
<p>{{ prezzo | currency:'EUR' }}</p>
<p>{{ nome | uppercase }}</p>Il dato resta intatto nel componente; a cambiare è solo la presentazione. È la
differenza tra tenere prezzo = 59.99 e ritrovarsi nel componente una stringa
"59,99 €" che poi non puoi più usare per fare i conti.
Le pipe pronte all'uso che uso più spesso:
date: formatta date, con locale e formato configurabilicurrency: numeri come valutauppercase/lowercase/titlecase: maiuscole e minuscolenumber: separatori delle migliaia e decimalijson: stampa un oggetto come JSON, preziosa in fase di debugasync: si sottoscrive a un Observable per te. Se nel capitolo 7 ilsubscribe()manuale ti era sembrato rumoroso, questa è l'alternativa
Anche le pipe vanno importate nel componente che le usa, come tutto nel mondo standalone:
imports: [DatePipe, CurrencyPipe]e così via. Se il template ti dà errore su una pipe "che dovrebbe esserci", è quasi sempre questo.
✍️ Scrivere una pipe personalizzata
Quando le pipe di serie non bastano, scriverne una è questione di minuti. Esempio classico: accorciare un testo troppo lungo.
1import { Pipe, PipeTransform } from '@angular/core';
2
3@Pipe({ name: 'shorten' })
4export class ShortenPipe implements PipeTransform {
5 transform(value: string, limit = 10): string {
6 return value.length > limit ? value.slice(0, limit) + '…' : value;
7 }
8}Nel template, dopo averla importata:
<p>{{ descrizione | shorten:20 }}</p>Un dettaglio che spiega perché le pipe sono così efficienti: di default sono pure, cioè Angular le riesegue solo quando il valore in ingresso cambia. Puoi metterne venti in una lista senza pensarci: non è lì che l'app rallenta.
Dove siamo arrivati
Con interceptor e pipe il modulo base è completo: componenti, router, signals, form, HTTP, stato condiviso e ora anche gli attrezzi trasversali per richieste e presentazione. Tutto quello che segue è mestiere e pratica.
E la pratica arriva subito: nel prossimo capitolo, l'ultimo della serie, mettiamo insieme ogni singolo pezzo e costruiamo un'app completa: una libreria digitale di videogiochi. Porta i dadi.