È finalmente uscito Angular 22 e questa release scende in campo con una serie di novità interessanti, tutte legate all’ecosistema dei Signal e all’ottimizzazione delle nostre webapp. In questo articolo vediamo le novità più importanti da conoscere
Signal Forms diventa stabile
Partiamo da una delle novità più rilevanti: i Signal Forms sono stabili e pronti per la produzione, sperimentali in Angular 21, ora si possono usare senza riserve.
Per i nuovi form possiamo abbandonare FormGroup, FormControl e valueChanges: i Signal Forms uniscono i Reactive Forms, la tipizzazione forte e la reattività dei Signal in un’unica API dichiarativa e componibile.
Il cuore è la funzione form, che riceve un Signal con i dati del form e uno schema opzionale di regole di validazione
import { signal } from '@angular/core';
import { form, required, minLength } from '@angular/forms/signals';
@Component({ imports: [FormField] /* ... */ })
export class JediForm {
protected readonly jedi = signal({ name: '', rank: '' });
protected readonly myForm = form(this.jedi, (path) => {
required(path.name, { message: 'Il nome è obbligatorio' });
minLength(path.name, 3);
});
}
Il risultato è un FieldTree: una struttura di Signal annidati dove ogni campo espone il proprio stato (value, dirty, invalid, errors). Nel template lo colleghiamo con la direttiva FormField:
<input [formField]="myForm.name" id="jedi-name" />
@if (myForm.name().invalid() && myForm.name().touched()) {
@for (error of myForm.name().errors(); track error.kind) {
<span class="error">{{ error.message }}</span>
}
}
Intorno a questo abbiamo uno stack completo: la Submission API per gestire l’invio, gli schemi dinamici con validateStandardSchema (compatibili con Zod e Valibot), le classi CSS condizionali e l’interop con i Reactive Forms esistenti. La guida ufficiale aggiornata è su angular.dev/guide/forms.
Da sviluppatore Angular, reputo molto interessante la gestione della validazione tramite schema zod, soprattutto in applicazioni enterprise dove le validazioni possono mutare in funzione di svariate variabili.
Le Resource API diventano stabili
resource, httpResource e rxResource escono dallo stato sperimentale. La Resource API era l’anello mancante dell’ecosistema Signal e ci permette di derivare dati in modo reattivo e asincrono, tipicamente chiamate HTTP GET che ripartono al variare di un Signal. Un esempio di utilizzo è una lista che va ricaricata al variare di alcuni filtri popolati dall’utente
Il punto d’ingresso più comodo è httpResource: riceve una lambda che restituisce la richiesta. Se un Signal usato al suo interno cambia, la richiesta riparte da sola.
import { httpResource } from '@angular/common/http';
import { signal } from '@angular/core';
export class ShipSearch {
readonly faction = signal('rebel');
readonly ships = httpResource<Ship[]>(
() => ({
url: 'https://api.example.io/ships',
params: { faction: this.faction() },
}),
{ defaultValue: [] },
);
}
La resource gestisce lo stato tramite Signal: value contiene i dati, error l’eventuale errore, isLoading lo stato di caricamento (c’è anche uno status più dettagliato). Il defaultValue ci evita di ritrovarci con undefined all’avvio.
ℹ️ Le race condition sono gestite in automatico: se arrivano più richieste ravvicinate, viene usata solo l’ultima e le precedenti vengono annullate, come farebbe uno switchMap in RxJS.
Nuovo decoratore @Service
E’ stato introdotto un nuovo decoratore @Service, che sostituisce il pattern più diffuso in assoluto, ovvero @Injectable({ providedIn: 'root' }), ed esprime meglio l’intento di registrare un servizio con scope globale.
import { Service } from '@angular/core';
@Service()
export class FleetStore { /* ... */ }
Di default il servizio è fornito nel root scope. @Injectable resta comunque disponibile per i casi che richiedono configurazioni più profonde o constructor injection.
OnPush di default
Da Angular 22 tutti i nuovi componenti usano OnPush in automatico come strategia per rilevare cambiamenti. La scelta è in linea con le dinamiche di aggiornamento introdotte con i Signal e l’abbandono di zone.js.
// Da Angular 22 questo componente usa OnPush, senza dichiararlo
@Component({
selector: 'app-dashboard',
template: `...`,
})
export class Dashboard {}
Il vecchio Default è stato rinominato in Eager, un nome che spiega meglio cosa fa: controlla l’intero albero dei componenti per rilevare eventuali modifiche.
@Component({
selector: 'app-legacy',
changeDetection: ChangeDetectionStrategy.Eager,
template: `...`,
})
export class Legacy {}
L’aggiornamento non comporta breaking change: per i componenti senza strategia esplicita, l’ng update aggiunge automaticamente Eager per preservare il comportamento precedente. La migrazione a OnPush la possiamo fare con calma, un componente alla volta.
injectAsync: lazy loading dei servizi
Questa è un’altra delle novità che più mi ha gasato: Angular supportava già il lazy loading di componenti e route, ma non dei servizi. Con injectAsync il lazy loading dei servizi diventa una feature out of the box del framework: il bundle viene scaricato solo alla prima invocazione, alleggerendo e velocizzando il caricamento del componente.
import { injectAsync } from '@angular/core';
export class Report {
private exporter = injectAsync(() => import('./report-exporter'));
async export() {
const exporter = await this.exporter();
exporter.export();
}
}
⚠️ Passare direttamente l’import funziona solo se il modulo espone un default export. In caso contrario va indicato l’export esplicito, ad esempio injectAsync(() => import('./report-exporter').then(m => m.ReportExporter)).
Per evitare il ritardo della prima volta, possiamo fare prefetch con onIdle, che carica il servizio in background mentre il browser non ha nulla da fare:
import { injectAsync, onIdle } from '@angular/core';
private exporter = injectAsync(
() => import('./report-exporter'),
{ prefetch: onIdle },
);
Perché funzioni, il servizio deve essere auto-provided (con @Service() o con @Injectable({ providedIn: 'root' })).
Questa feature la possiamo sfruttare per caricare in modalità lazy tutti quei servizi necessari al componente ma che non hanno un ruolo di primaria importanza nel caricamento del componente, magari legato a funzionalità che l’utente potrebbe usare.
FetchBackend di default nell’HttpClient
Da ora l’HttpClient usa di default la Fetch API al posto di XMLHttpRequest, quindi il vecchio withFetch() è deprecato e lo possiamo rimuovere. Fetch è più moderno, basata su Promise e più adatta agli scenari SSR (Server-Side-Rendering).
C’è un limite da tenere presente: Fetch non supporta gli eventi di progresso in upload. Per questo l’opzione generica reportProgress viene sostituita da due varianti dedicate:
// Progresso in download (funziona con Fetch)
http.get('/file', { reportDownloadProgress: true, observe: 'events' });
// Progresso in upload (richiede withXhr())
http.post('/upload', file, { reportUploadProgress: true, observe: 'events' });
Se ci serve il progresso in upload, torniamo a XHR con provideHttpClient(withXhr()) (l’ng update lo aggiunge dove necessario).
Angular Aria diventa stabile
Angular Aria passa in produzione: sono 12 pattern di accessibilità headless (accordion, listbox, menu, tree e altri) pronti all’uso. L’approccio è quello di lasciare a noi gli stili e la logica di business, mentre Angular Aria si occupa dell’accessibilità.
In questa versione le API sono state stabilizzate, sono stati aggiunti i test harness e l’integrazione completa con i Signal Forms. La panoramica dei pattern è su angular.dev/guide/aria/overview.
Angular MCP: i nuovi tool per il devserver
Sul fronte AI, il server MCP della CLI guadagna tre nuovi tool per interagire con il development server:
devserver.startper avviare il serverdevserver.stopper fermarlodevserver.wait_for_buildper attendere la build e leggerne l’output
Il più interessante è devserver.wait_for_build: permette a un agente AI di compilare il progetto, leggere i log e decidere i passi successivi in base agli errori trovati, abilitando dei loop di auto-correzione. Questi tool diventano stabili in questa release, insieme a quelli per test ed end-to-end.
Template: migliorie alla sintassi
I template diventano più espressivi e ci permettono di delegare meno al componente. Le novità principali:
- Commenti
//e/* */nei template dei componenti, utili per documentare proprietà e binding. - Spread/rest in oggetti, array e chiamate di funzione, come in TypeScript.
- Arrow function inline negli event binding.
@casemultipli consecutivi nello@switch, per evitare duplicazioni.- Check esaustivo con
@default never: se aggiungiamo un valore all’union senza gestirlo, la compilazione fallisce.
Qualche esempio:
<div
// bottone primario
class="btn btn-primary"
/* disabilitato solo durante il loading */
[disabled]="loading()"
></div>
<div [class]="{ ...baseClasses, active: isActive() }"></div>
@switch (orderStatus) {
@case ('pending')
@case ('processing') { <p>In corso</p> }
@case ('shipped') { <p>Spedito</p> }
@default never;
}
Incremental hydration di default
provideClientHydration() abilita in automatico l’incremental hydration, senza configurazione aggiuntiva. Se non ci serve, la disattiviamo esplicitamente con withNoIncrementalHydration().
Le agent skill ufficiali di Angular
Tenere il passo con una superficie di API che cresce è difficile non solo per noi, ma anche per gli assistenti AI, i cui dati di training spesso non coprono i pattern più recenti e generano codice “obsoleto” già in partenza. Per colmare il gap, il team Angular ha rilasciato delle skill ufficiali che insegnano agli agenti i pattern moderni del framework:
angular-developer: best practice e linee guida per scrivere applicazioni Angular moderne, comprese feature recenti come Angular Aria e Signal Forms.angular-new-app: pensata per chi parte da zero, guida l’assistente nella configurazione di un ambiente Angular locale.
Le trovate su github.com/angular/skills e funzionano in ambienti agentici. Io personalmente utilizzo già da diverso tempo queste skill con Claude Code o Github Copilot
Webpack deprecato
Cambio di rotta importante sul build: in v22 sono deprecati @angular-devkit/build-angular, @ngtools/webpack e il supporto a Webpack. Il loro posto è preso dall’application builder basato su esbuild e Rollup (pacchetto @angular/build), da tempo l’opzione di default per i nuovi progetti.
Su un fronte diverso, il team sta esplorando TSGo, il compilatore TypeScript riscritto in Go. Non è un bundler né il sostituto di Webpack, ma punta a velocizzare type checking e compilazione: è un lavoro ancora in fase iniziale e non legato direttamente all’application builder, ma che in futuro ci permetterà di abbattere enormemente il tempo di compilazione e build di angular.
debounced() (sperimentale)
I Signal, per loro natura, non hanno il concetto di tempo: niente delay, niente throttling. Le Resource invece sì, ed è qui che si inserisce la nuova funzione debounced (ancora sperimentale in v22, quindi l’API potrebbe cambiare): un debounce signal-first che crea una Resource<T> con il valore “ritardato”, lo stato di loading durante l’attesa e la gestione degli errori integrata.
import { debounced } from '@angular/core';
const filter = signal('');
const debouncedFilter = debounced(filter, 300); // 300ms
effect(() => console.log(debouncedFilter.value()));
WebMCP (sperimentale)
Tra le feature sperimentali segnalo WebMCP. Permette a un’applicazione Angular di esporre tool strutturati agli agenti AI direttamente nel browser, senza ricorrere all’automazione del DOM. Possiamo definire tool a livello di intera app, di route o di componente, ed è prevista anche la generazione automatica dei tool a partire dai Signal Forms dinamici.
È una feature sperimentale: al momento richiede Chrome Beta con il relativo flag abilitato. È documentata su angular.dev/ai/webmcp.
Uno sguardo al futuro: @boundary
Non è ancora disponibile, ma il team ha anticipato @boundary, le error boundary direttamente nei template: avvolgiamo un blocco “a rischio” e, se un componente va in errore, invece di rompere l’intera pagina mostriamo un fallback.
@boundary {
<app-promo-widget />
}
@error (let err) {
<app-default-promo />
}
Le tempistiche non sono ancora confermate (probabilmente Angular 22.1 o 23) e la sintassi qui sopra è indicativa, potrebbe cambiare. Ne riparleremo quando sarà il momento, ma ci tenevo ad includerlo perchè apre scenari implementativi molto interessanti
Conclusioni
Angular 22 è una release di maturità nell’era dei Signal: con Signal Forms, Resource API e Angular Aria finalmente stabili abbiamo dei mattoni fondamentali pronti per la produzione, mentre @Service, injectAsync, debounced, le migliorie ai template e il lavoro sul fronte AI completano il quadro.
Conviene aggiornare subito? E cosa facciamo se abbiamo già applicazioni complesse che non usano i Signal? Dalla mia esperienza il consiglio è di valutare caso per caso: l’aggiornamento di per sé è sicuro (il comportamento del change detection viene preservato in automatico), mentre l’adozione delle nuove primitive conviene introdurla gradualmente, sulle nuove evolutive e sui refactoring. Il codice basato su RxJS non è da buttare via, ma la direzione del framework è ormai chiaramente quella dei Signal. In altri casi dovremo sicuramente aspettare gli eventuali aggiornamenti di altre librerie, per esempio NX (ma di solito sono velocissimi e nel giro di qualche giorno rendono disponibile la versione aggiornata all’ultima release Angular)
Grazie per aver letto, alla prossima! 😉