Torna al blog

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.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

8 min di lettura
Angular 21: Nuove Funzionalità e Miglioramenti

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 ControlValueAccessor necessario
  • 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:

  1. list_projects: Elenca progetti Angular nel workspace
  2. get_best_practices: Fornisce best practices Angular
  3. find_examples: Trova esempi di codice per pattern specifici
  4. ai_tutor: Tutor interattivo AI per Angular
  5. generate_component: Genera componenti da descrizioni natural language
  6. onpush_zoneless_migration: Assiste nella migrazione a zoneless
  7. 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 → Usa provideRouter in test
  • web-test-runner builder → Usa Vitest
  • jest builder → 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

💡 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