Angular Zoneless: i signals ed il tramonto di Zone.js

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.

Cos’è un Signal? (In breve):

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:

  • Getter: Per leggere il valore.
  • Setter/Update: Per aggiornare lo stato in modo esplicito.
  • Computed: Per derivare nuovi dati con memoizzazione automatica (si ricalcolano solo se le dipendenze cambiano).

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);
  }
}

L’eredità di Zone.js: Perché è stato fondamentale?

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 vantaggio: Angular sapeva quando qualcosa era successo e avviava la change detection.
  • Il limite: Zone.js, non sapendo esattamente cosa è cambiato, costringe Angular a controllare l'intero albero dei componenti (o gran parte di esso), causando un overhead computazionale evidente in applicazioni complesse e rendendo il debug dei cicli di aggiornamento un incubo.

Il cambiamento: Change Detection mirata

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:

  • Tracciamento delle dipendenze: quando un Signal viene letto in un template, Angular registra quel componente come "interessato".
  • Aggiornamenti precisi: se il Signal cambia, Angular sa esattamente quale parte del DOM aggiornare. Non deve più "chiedere" a ogni componente se ha novità; è il dato stesso a bussare alla porta della view corretta.

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.");
  }
}

Perché questo approccio vince?

  • Performance: Meno cicli di CPU e latenza ridotta grazie all'assenza del patching globale.
  • Bundle Size: Rimuovere Zone.js riduce il peso finale del pacchetto JavaScript inviato al browser.
  • Predicibilità: Il flusso dei dati è chiaro. Sappiamo sempre perché e quando una view si aggiorna, facilitando il testing e il Server Side Rendering (SSR), che diventa molto più stabile e veloce

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.

Autoreadmin
Potrebbero interessarti...
back to top icon