Come Angular v21 sta ridisegnando la change detection: meno overhead, più controllo, bundle più leggeri.

Uno tra gli ultimi importanti aggiornamenti di Angular (v21), rilasciato a novembre 2025, segna un punto di svolta cruciale nell'evoluzione e nella maturità del framework. Grazie al lavoro del team di Google, Angular sta accelerando la transizione verso un modello reattivo granulare basato sui Signals, avviando concretamente il superamento graduale di Zone.js. La modalità Zoneless è già il nuovo standard per i progetti creati da zero, mentre le applicazioni esistenti possono continuare ad utilizzare Zone.js, che rimane ancora pienamente supportato. Ma cosa comporta questo cambiamento per le performance e il developer workflow? Per capirlo, dobbiamo analizzare come la gestione della change detection stia cambiando pelle.
Un Signal è un wrapper attorno a un valore che notifica ai suoi "consumatori" ogni volta che cambia. Possiamo immaginarlo come una cella di un foglio di calcolo: quando il valore cambia, tutte le celle collegate si aggiornano automaticamente .
A differenza delle variabili standard, i Signals offrono:
Ecco un esempio di signal
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
standalone: true,
template: `
<p>Il valore attuale è: {{ conteggio() }}</p>
<button (click)="incrementa()">+1</button>
`
})
export class CounterComponent {
// 1. Dichiariamo il Signal con un valore iniziale (0)
conteggio = signal(0);
incrementa() {
// 2. Aggiorniamo il valore
this.conteggio.update(val => val + 1);
}
}
Per anni, Zone.js è stato il "motore invisibile" di Angular. Il suo compito era fare il monkey-patching di tutte le API asincrone del browser (come setTimeout o le chiamate Fetch).
Il passaggio ai Signals sposta il paradigma da implicito a esplicito.
In un'architettura Zoneless, Angular smette di intercettare ogni evento asincrono globale. Invece, si mette in ascolto dei Signals:
Esempio prima di introdurre i signal
// COMPONENTE PADRE
@Component({
selector: 'app-parent',
template: `
<h1>Timer: {{ counter }}</h1>
<app-heavy-child [data]="staticData" />
`
})
export class ParentComponent {
counter = 0;
staticData = { id: 1, label: 'Fisso' };
constructor() {
// Zone.js intercetta il timer
setInterval(() => {
this.counter++;
// EFFETTO: Angular controlla Parent E POI controlla anche HeavyChild
}, 1000);
}
}
// COMPONENTE FIGLIO PESANTE
@Component({
selector: 'app-heavy-child',
template: `<p>Dati: {{ data.label }}</p>`
})
export class HeavyChildComponent {
@Input() data: any;
// Questo metodo verrebbe chiamato a ogni ciclo di CD predefinito
get checkPerformance() {
console.log("Zone.js mi sta controllando inutilmente...");
return true;
}
}
esempio dopo l'introduzione dei signal// COMPONENTE PADRE (Signal-based)
@Component({
standalone: true,
template: `
<h1>Timer: {{ counter() }}</h1>
<app-heavy-child [data]="staticData()" />
`
})
export class ParentComponent {
counter = signal(0);
staticData = signal({ id: 1, label: 'Fisso' });
constructor() {
setInterval(() => {
this.counter.update(v => v + 1);
// EFFETTO: Solo l'elemento <h1> nel DOM viene toccato.
// Angular "salta" completamente il controllo di <app-heavy-child>.
}, 1000);
}
}
// COMPONENTE FIGLIO PESANTE
@Component({
selector: 'app-heavy-child',
standalone: true,
template: `<p>Dati: {{ data().label }}</p>`
})
export class HeavyChildComponent {
data = input.required<any>(); // Signal Input
checkPerformance() {
// Silenzio totale in console. Il componente è "dormiente" e performante.
console.log("Io vengo eseguito solo se 'data' cambia davvero.");
}
}
In conclusione, il passaggio ad un approccio "Zoneless" basato sui Signals rappresenta un cambio di paradigma, evolvendo da un monitoraggio globale che "indovina" i cambiamenti a una reattività che li "osserva" chirurgicamente solo dove e quando necessario. Questa trasformazione non solo elimina l'eredità di Zone.js, ma garantisce applicazioni più leggere, prevedibili e performanti, riducendo drasticamente la complessità della Change Detection.