Angular 21: Nuove Funzionalità e Miglioramenti
Scopri Angular 21: Signal Forms, zoneless di default, Vitest, Angular Aria, MCP Server AI e tutte le novità del rilascio di novembre 2025.

Angular 21 è stato rilasciato il 20 novembre 2025 e rappresenta una delle release più significative nella storia del framework. Questa versione introduce Signal Forms, zoneless change detection di default, Vitest come test runner standard e il rivoluzionario MCP Server per l'integrazione AI.
🎯 Novità Principali
Signal Forms (Sperimentale)
La feature più attesa: forms completamente basati su signals:
import { Component } from "@angular/core";
import { form, field, FormModel } from "@angular/forms/signals";
@Component({
selector: "app-user-form",
standalone: true,
template: `
<form [formModel]="userForm">
<input [field]="userForm.controls.email" />
<span>{{ userForm.controls.email.error() }}</span>
<input [field]="userForm.controls.age" type="number" />
<button [disabled]="!userForm.valid()">Submit</button>
</form>
<pre>{{ userForm.value() | json }}</pre>
`,
})
export class UserFormComponent {
userForm = form({
email: field("", {
validators: [(value) => (!value.includes("@") ? "Invalid email" : null)],
}),
age: field(0, {
validators: [(value) => (value < 18 ? "Must be 18+" : null)],
}),
});
ngOnInit() {
// Reattività automatica
effect(() => {
console.log("Form value:", this.userForm.value());
console.log("Is valid:", this.userForm.valid());
});
}
}
Vantaggi rispetto a ReactiveForm:
- Nessun
ControlValueAccessornecessario - Type safety completa automatica
- Reattività nativa con signals
- Meno boilerplate
- Validation schema-based
Form Complessi con Signal Forms
import { Component } from "@angular/core";
import { form, field, array } from "@angular/forms/signals";
interface Address {
street: string;
city: string;
zip: string;
}
@Component({
selector: "app-complex-form",
template: `
<form [formModel]="profileForm">
<!-- Campo semplice -->
<input [field]="profileForm.controls.username" />
<!-- Nested form group -->
<div [formModel]="profileForm.controls.address">
<input [field]="profileForm.controls.address.controls.street" />
<input [field]="profileForm.controls.address.controls.city" />
<input [field]="profileForm.controls.address.controls.zip" />
</div>
<!-- Form array -->
@for (phone of profileForm.controls.phones.controls(); track $index) {
<input [field]="phone" />
<button (click)="removePhone($index)">Remove</button>
}
<button (click)="addPhone()">Add Phone</button>
<button (click)="submit()">Submit</button>
</form>
`,
})
export class ComplexFormComponent {
profileForm = form({
username: field(""),
address: form({
street: field(""),
city: field(""),
zip: field(""),
}),
phones: array([field(""), field("")]),
});
addPhone() {
this.profileForm.controls.phones.push(field(""));
}
removePhone(index: number) {
this.profileForm.controls.phones.removeAt(index);
}
submit() {
if (this.profileForm.valid()) {
console.log(this.profileForm.value());
}
}
}
Zoneless di Default
Angular 21 disabilita Zone.js di default per nuovi progetti:
// main.ts - Zoneless di default!
import { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app/app.component";
bootstrapApplication(AppComponent, {
providers: [
// provideExperimentalZonelessChangeDetection()
// NON più necessario - è il default!
],
});
Vantaggi Zoneless:
- Performance: Fino al 30% più veloce
- Bundle size: -15KB (rimozione di Zone.js)
- Debugging: Più semplice senza Zone.js
- Compatibilità: Migliore con altre librerie
Migrazione Automatica:
# Angular CLI include uno schematic per la migrazione
ng generate @angular/core:zoneless
Vitest Come Test Runner Standard
Vitest sostituisce Karma/Jasmine di default:
// user.component.spec.ts
import { describe, it, expect } from "vitest";
import { TestBed } from "@angular/core/testing";
import { UserComponent } from "./user.component";
describe("UserComponent", () => {
it("should create", () => {
TestBed.configureTestingModule({
imports: [UserComponent],
});
const fixture = TestBed.createComponent(UserComponent);
const component = fixture.componentInstance;
expect(component).toBeTruthy();
});
it("should display user name", async () => {
const fixture = TestBed.createComponent(UserComponent);
fixture.componentRef.setInput("name", "Mario");
await fixture.whenStable();
const element = fixture.nativeElement;
expect(element.textContent).toContain("Mario");
});
});
Nuovo progetto con Vitest:
# Vitest è il default
ng new my-app
# O esplicitamente
ng new my-app --test-runner=vitest
# Karma ancora disponibile se necessario
ng new my-app --test-runner=karma
Vantaggi Vitest:
- Velocità: 5-10x più veloce di Karma
- HMR: Test si aggiornano istantaneamente
- Browser mode: Test in browser reale
- Compatibilità Jest: Sintassi familiare
Angular Aria (Developer Preview)
Nuova libreria per componenti accessibili headless:
import { Component } from "@angular/core";
import { CdkListbox, CdkOption } from "@angular/cdk-experimental/listbox";
@Component({
selector: "app-custom-select",
standalone: true,
imports: [CdkListbox, CdkOption],
template: `
<div cdkListbox [(value)]="selectedValue">
@for (option of options; track option.value) {
<div cdkOption [value]="option.value" class="option">
{{ option.label }}
</div>
}
</div>
<p>Selected: {{ selectedValue }}</p>
`,
styles: [
`
.option {
padding: 8px;
cursor: pointer;
}
.option:hover {
background: #f0f0f0;
}
.option[aria-selected="true"] {
background: #007bff;
color: white;
}
`,
],
})
export class CustomSelectComponent {
selectedValue = "option1";
options = [
{ value: "option1", label: "Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
];
}
Componenti Angular Aria disponibili:
- Accordion
- Autocomplete
- ComboBox
- Grid
- ListBox
- Menu
- Multiselect
- Select
- Tabs
- Toolbar
- Tree
MCP Server per AI (Stabile)
Angular 21 include un server MCP (Model Context Protocol) per integrazione AI:
# Installa MCP Server
npm install -g @angular/cli-mcp-server
# Avvia il server
ng-mcp-server
7 Tool Disponibili:
- list_projects: Elenca progetti Angular nel workspace
- get_best_practices: Fornisce best practices Angular
- find_examples: Trova esempi di codice per pattern specifici
- ai_tutor: Tutor interattivo AI per Angular
- generate_component: Genera componenti da descrizioni natural language
- onpush_zoneless_migration: Assiste nella migrazione a zoneless
- analyze_bundle: Analizza dimensioni bundle e suggerisce ottimizzazioni
Esempio uso con Claude/ChatGPT:
Tu: "Crea un componente Angular per visualizzare una lista di utenti con paginazione"
AI (usando MCP Server):
- Usa find_examples per pattern simili
- Usa get_best_practices per signal inputs
- Genera codice ottimizzato con Signal Forms
🚀 HttpClient Standalone di Default
HttpClient ora disponibile automaticamente:
// ❌ Prima - import necessario
import { provideHttpClient } from "@angular/common/http";
bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
});
// ✅ Angular 21 - automatico!
bootstrapApplication(AppComponent);
// HttpClient già disponibile ovunque
@Component({
selector: "app-users",
standalone: true,
})
export class UsersComponent {
private http = inject(HttpClient); // Funziona immediatamente!
users = signal<User[]>([]);
async ngOnInit() {
const data = await this.http.get<User[]>("/api/users").toPromise();
this.users.set(data);
}
}
🎨 Miglioramenti Template
@defer con Opzioni Viewport
@Component({
template: `
@defer (on viewport({
trigger: myElement,
rootMargin: '100px'
})) {
<heavy-component />
} @placeholder {
<div>Loading...</div>
}
<div #myElement>Trigger element</div>
`
})
SimpleChanges Generici
import { Component, Input, SimpleChanges, OnChanges } from "@angular/core";
@Component({
selector: "app-user",
template: `<p>{{ name }}</p>`,
})
export class UserComponent implements OnChanges {
@Input() name!: string;
@Input() age!: number;
// SimpleChanges ora è generico!
ngOnChanges(changes: SimpleChanges<this>) {
if (changes.name) {
// Type-safe: changes.name.currentValue è string
console.log(changes.name.currentValue.toUpperCase());
}
if (changes.age) {
// Type-safe: changes.age.currentValue è number
console.log(changes.age.currentValue.toFixed(2));
}
}
}
KeyValue Pipe con Chiavi Opzionali
interface User {
name: string;
email?: string;
phone?: string;
}
@Component({
template: `
@for (prop of user | keyvalue; track prop.key) {
<p>{{ prop.key }}: {{ prop.value }}</p>
}
`,
})
export class UserDetailsComponent {
user: User = {
name: "Mario",
email: "mario@example.com",
// phone opzionale
};
}
🛠️ Angular Material & CDK
Token-Based Theming
// Nuove utility classes per Material Design tokens
@Component({
template: `
<button class="mat-sys-primary mat-sys-on-primary">
Primary Button
</button>
<div class="mat-sys-surface mat-sys-on-surface">
Surface content
</div>
`
})
CDK Overlays con Popover Nativo
import { Component } from "@angular/core";
import { CdkOverlayOrigin, CdkConnectedOverlay } from "@angular/cdk/overlay";
@Component({
selector: "app-menu",
template: `
<button
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
(click)="isOpen = !isOpen">
Open Menu
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayUseNativePopover]="true">
<div class="menu">
<button>Item 1</button>
<button>Item 2</button>
</div>
</ng-template>
`,
})
export class MenuComponent {
isOpen = false;
}
🔧 CLI Improvements
Tailwind CSS Schematic
# Setup Tailwind in un comando
ng add @angular/tailwind
# O durante la creazione
ng new my-app --style=tailwind
Define per Development Server
// angular.json
{
"serve": {
"options": {
"define": {
"VERSION": "process.env.APP_VERSION"
}
}
}
}
// Uso nel codice
declare const VERSION: string;
console.log("App version:", VERSION);
🔄 Come Aggiornare ad Angular 21
Aggiornamento Automatico
# Metodo consigliato
ng update @angular/core@21 @angular/cli@21
# Con migrazioni automatiche
ng update @angular/core@21 @angular/cli@21 --allow-dirty
Migrazione a Zoneless
# Genera migrazione automatica
ng generate @angular/core:zoneless
# Verifica compatibilità
ng lint
Migrazione a Vitest
# Se hai un progetto esistente con Karma
ng generate @angular/core:vitest-migration
⚠️ Breaking Changes
Zone.js Non Più Incluso di Default
// Se hai bisogno di Zone.js (legacy)
import { provideZoneChangeDetection } from "@angular/core";
bootstrapApplication(AppComponent, {
providers: [provideZoneChangeDetection()],
});
Deprecazioni
RouterTestingModule→ UsaprovideRouterin testweb-test-runnerbuilder → Usa Vitestjestbuilder → Usa Vitest
📊 Performance Comparison
| Metrica | Angular 20 | Angular 21 | Miglioramento |
|---|---|---|---|
| Initial Load | 2.1s | 1.5s | -29% |
| Change Detection | 12ms | 8ms | -33% |
| Bundle Size | 145KB | 123KB | -15% |
| Test Speed (1000 tests) | 45s | 9s | -80% |
🔗 Risorse Utili
- Angular 21 Release Blog
- Angular Update Guide
- Signal Forms Documentation
- Vitest Migration Guide
- Angular Aria Docs
💡 Conclusioni
Angular 21 è una release monumentale:
✅ Signal Forms rivoluzionano la gestione form ✅ Zoneless di default per performance superiori ✅ Vitest rende i test 10x più veloci ✅ Angular Aria per accessibilità nativa ✅ MCP Server porta Angular nell'era dell'AI ✅ HttpClient disponibile out-of-the-box
Quando aggiornare:
- ✅ Nuovi progetti: parti con Angular 21
- ⚠️ Progetti esistenti: testa Signal Forms e zoneless
- 📚 Enterprise: pianifica migrazione graduale