Angular Standalone Components: migrazione e best practice per Angular 22
Guida completa alla migrazione di applicazioni Angular dai NgModule agli standalone component. Copre la migrazione ufficiale CLI in 3 passaggi, i selectorless component, OnPush di default e lazy loading con loadComponent in Angular 22.

Gli standalone component di Angular eliminano la necessità dei NgModule, riducono il boilerplate e abilitano un lazy loading a grana fine in tutta l'applicazione. Da quando Angular 19 ha reso lo standalone il default e Angular 22 ha introdotto i selectorless component come stabili, migrare codebase legacy basate su moduli è diventato sia semplice sia ad alto impatto.
Lo schematic ufficiale dell'Angular CLI gestisce automaticamente la maggior parte della migrazione in tre passaggi. Una tipica applicazione enterprise può completare la conversione in un singolo sprint, con bundle che si riducono del 30-50 % grazie al lazy loading per componente.
NgModule vs standalone component: cosa è cambiato
I NgModule hanno funzionato come contesto di compilazione per i componenti fin da Angular 2. Ogni componente, direttiva e pipe doveva essere dichiarato in esattamente un modulo, e la condivisione di funzionalità richiedeva import ed export di moduli orchestrati con cura. Ne risultava un forte accoppiamento tra feature non correlate e un tree-shaking difficoltoso.
Gli standalone component ribaltano questo modello. Ogni componente dichiara le proprie dipendenze direttamente nell'array imports del decoratore @Component. Niente registrazione nei moduli, niente moduli condivisi, niente barrel export di mezza applicazione. A partire da Angular 22, questo pattern è ulteriormente semplificato dai selectorless component, che permettono import diretti senza selettori stringa.
import { Component, signal } from '@angular/core';
import { HeroCardComponent } from './hero-card.component';
import { SearchPipe } from '../pipes/search.pipe';
@Component({
selector: 'app-hero-list',
standalone: true,
imports: [HeroCardComponent, SearchPipe],
template: \`
<div class="hero-grid">
@for (hero of heroes() | search:query(); track hero.id) {
<app-hero-card [hero]="hero" />
}
</div>
\`
})
export class HeroListComponent {
heroes = signal<Hero[]>([]);
query = signal('');
}L'array imports sostituisce l'intero grafo delle dipendenze dei NgModule. Il bundler vede esattamente quali componenti, pipe e direttive servono a ciascun file, abilitando un tree-shaking preciso. Il CommonModule non è più necessario in Angular 22 poiché la sintassi di control flow (@for, @if) è integrata nel compilatore.
Il processo di migrazione CLI in 3 passaggi
Angular fornisce uno schematic automatizzato che gestisce la migrazione in tre passaggi sequenziali. Ogni step si basa sul precedente, e tra un passaggio e l'altro il progetto deve compilare senza errori.
Passaggio 1: convertire le dichiarazioni in standalone
Il primo passaggio analizza ogni componente, direttiva e pipe del progetto, aggiunge standalone: true e sposta gli import necessari dal NgModule padre nell'array imports di ciascun componente.
# Step 1: Convert all declarations to standalone
ng generate @angular/core:standalone --path=src/appAlla richiesta, selezionare "Convert all components, directives and pipes to standalone". Lo schematic usa l'analisi statica per risolvere le dipendenze, quindi qualunque componente con metadata non analizzabili a build time verrà saltato con un warning.
Passaggio 2: rimuovere i NgModule non necessari
Con tutte le dichiarazioni ora standalone, molti NgModule diventano gusci vuoti. Questo passaggio identifica i moduli che si limitavano a riesportare dichiarazioni standalone e li rimuove.
# Step 2: Remove empty NgModules
ng generate @angular/core:standalone --path=src/appSelezionare "Remove unnecessary NgModule classes". I moduli che contengono ancora provider, configurazioni di rotte o sono importati da più moduli vengono mantenuti con un commento TODO per la revisione manuale.
Angular 22 richiede TypeScript 6 e Node.js 26. Le versioni precedenti non sono supportate. Eseguire ng version per verificare la compatibilità prima di iniziare la migrazione.
Passaggio 3: passare al bootstrap standalone
L'ultimo passaggio sostituisce il NgModule root con l'API bootstrapApplication di Angular e converte il componente root in standalone.
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';
bootstrapApplication(AppComponent, appConfig)
.catch(err => console.error(err));import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { routes } from './app.routes';
import { authInterceptor } from './interceptors/auth.interceptor';
export const appConfig: ApplicationConfig = {
providers: [
provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor]))
]
};Il pattern ApplicationConfig sostituisce gli array providers e imports del modulo root. Tutte le funzioni provider (provideRouter, provideHttpClient, provideAnimations) funzionano direttamente senza wrapper modulari.
Angular 22: OnPush di default e selectorless component
Angular 22 ha introdotto due cambiamenti che influenzano lo sviluppo con standalone component: OnPush come strategia di change detection predefinita e i selectorless component che raggiungono lo stato stabile.
OnPush di default
I nuovi componenti generati in Angular 22 usano ChangeDetectionStrategy.OnPush di default. Questo favorisce la reattività basata su signal rispetto ai controlli attivati da Zone.js. Durante la migrazione, lo schematic aggiunge esplicitamente changeDetection: ChangeDetectionStrategy.Default ai componenti esistenti per mantenere la retrocompatibilità.
// Angular 22: OnPush is implicit for new components
@Component({
selector: 'app-dashboard',
standalone: true,
// changeDetection: ChangeDetectionStrategy.OnPush is now the default
template: \`<h1>{{ title() }}</h1>\`
})
export class DashboardComponent {
title = signal('Dashboard');
}Per le applicazioni migrate, si consiglia di adottare OnPush in modo incrementale sostituendo le proprietà di classe con signal. Questo si allinea con l'architettura signal-first promossa da Angular 22.
Selectorless component (stabili)
I selectorless component permettono di importare componenti direttamente nei template senza definire selettori stringa. Questo elimina i conflitti di naming dei selettori nelle grandi codebase e fornisce una migliore type safety.
// Traditional approach with selector
@Component({
selector: 'app-user-card',
standalone: true,
template: \`<div class="user-card">{{ user().name }}</div>\`
})
export class UserCardComponent {
user = input.required<User>();
}
// Selectorless approach (Angular 22+)
@Component({
standalone: true,
template: \`<div class="user-card">{{ user().name }}</div>\`
})
export class UserCardComponent {
user = input.required<User>();
}Nei template, i selectorless component vengono usati tramite il loro nome di classe invece di un tag selettore personalizzato. Il compilatore verifica che l'import esista, intercettando gli errori a build time invece che a runtime.
Migrazione del routing: dai moduli a loadComponent
I moduli di routing richiedono attenzione manuale perché lo schematic non converte automaticamente gli import di loadChildren di moduli in loadComponent o in loadChildren a livello di rotta con rotte standalone.
Il pattern legacy caricava interi moduli di feature:
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module')
.then(m => m.DashboardModule)
}
];L'equivalente standalone carica direttamente singoli componenti o file di rotte:
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'dashboard',
loadComponent: () => import('./dashboard/dashboard.component')
.then(c => c.DashboardComponent)
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.routes')
.then(r => r.settingsRoutes)
}
];import { Routes } from '@angular/router';
export const settingsRoutes: Routes = [
{
path: '',
loadComponent: () => import('./settings.component')
.then(c => c.SettingsComponent),
children: [
{
path: 'profile',
loadComponent: () => import('./profile/profile.component')
.then(c => c.ProfileComponent)
},
{
path: 'security',
loadComponent: () => import('./security/security.component')
.then(c => c.SecurityComponent)
}
]
}
];loadComponent carica in lazy un singolo componente. loadChildren con un file di rotte carica in lazy un'intera area di feature. Entrambi producono chunk separati che il browser scarica su richiesta.
Pronto a superare i tuoi colloqui su Angular?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Gestire SharedModule e dipendenze comuni
Gli SharedModule, i moduli contenitore che esportano componenti, direttive e pipe di uso comune, sono il blocco più frequente durante la migrazione. Lo schematic non riesce a rimuoverli automaticamente perché molti moduli li importano.
La soluzione: convertire le dichiarazioni condivise in standalone una a una, poi eliminare lo SharedModule quando nessuno lo importa più.
// Before: SharedModule re-exports everything
@NgModule({
declarations: [LoadingSpinner, TooltipDirective, TruncatePipe],
exports: [LoadingSpinner, TooltipDirective, TruncatePipe],
imports: [CommonModule]
})
export class SharedModule {}
// After: Each declaration is standalone, import directly
// loading-spinner.component.ts
@Component({
selector: 'app-loading-spinner',
standalone: true,
template: \`<div class="spinner" role="status"></div>\`
})
export class LoadingSpinner {}I consumatori importano direttamente LoadingSpinner invece dell'intero SharedModule. Il bundler include solo i componenti effettivamente necessari a ciascuna rotta.
Imporre lo sviluppo solo standalone
Dopo la migrazione è essenziale impedire che nuovi NgModule rientrino nella codebase. Angular fornisce un'opzione del compilatore TypeScript dedicata.
{
"angularCompilerOptions": {
"strictStandalone": true
}
}Con strictStandalone attivo, ogni tentativo di creare un componente, direttiva o pipe non standalone produce un errore di compilazione. Questo impone la nuova architettura a tutto il team. La migrazione di Angular Forms segue lo stesso pattern, con Signal Forms ora stabile in Angular 22.
Guadagni di performance: dimensioni del bundle e lazy loading
Il principale beneficio prestazionale degli standalone component arriva dal lazy loading granulare. Con i NgModule il lazy loading agiva a livello di modulo: importare un componente da un modulo trascinava ogni dichiarazione esportata da quel modulo. Gli standalone component spezzano questo accoppiamento.
Un benchmark reale su un'applicazione enterprise di media dimensione (oltre 200 componenti) ha mostrato:
| Metrica | Basato su NgModule | Standalone | Miglioramento |
|---|---|---|---|
| Bundle iniziale | 485 KB | 218 KB | -55 % |
| Chunk lazy più grande | 142 KB | 38 KB | -73 % |
| Time to Interactive | 3,2 s | 1,8 s | -44 % |
| Tempo di build (esbuild) | 12,4 s | 8,1 s | -35 % |
Questi numeri derivano dalla rimozione dell'overhead di risoluzione dei moduli e dalla capacità del bundler di eliminare gli export inutilizzati a livello di componente invece che a livello di modulo.
Testare gli standalone component con Vitest
Gli unit test si semplificano notevolmente con gli standalone component. Angular 22 ha reso Vitest il test runner predefinito, sostituendo Karma. La configurazione di TestBed non richiede più l'import di interi moduli per soddisfare le dipendenze di un componente.
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeroListComponent } from './hero-list.component';
import { HeroService } from '../services/hero.service';
import { of } from 'rxjs';
describe('HeroListComponent', () => {
let fixture: ComponentFixture<HeroListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HeroListComponent],
providers: [
{ provide: HeroService, useValue: { getHeroes: () => of([]) } }
]
}).compileComponents();
fixture = TestBed.createComponent(HeroListComponent);
});
it('should render hero cards', () => {
fixture.componentRef.setInput('heroes', mockHeroes);
fixture.detectChanges();
const cards = fixture.nativeElement.querySelectorAll('app-hero-card');
expect(cards.length).toBe(mockHeroes.length);
});
});Il componente entra direttamente nell'array imports di TestBed.configureTestingModule. Tutte le dipendenze dichiarate sono già risolte tramite il proprio imports, quindi non servono import aggiuntivi di moduli.
Errori comuni durante la migrazione
Import circolari tra standalone component. Quando il componente A importa il B e B importa A, il compilatore TypeScript solleva un errore di dipendenza circolare. La soluzione: estrarre l'interfaccia condivisa in un file separato oppure usare forwardRef() come workaround temporaneo durante il refactoring della catena di dipendenze.
Librerie di terze parti che usano ancora NgModule. Molte librerie sono migrate a standalone, ma alcuni pacchetti legacy esportano ancora NgModule. Questi moduli vanno importati direttamente nell'array imports del componente standalone, Angular supporta la mescolanza di import standalone e basati su moduli.
Provider mancanti dopo la rimozione di AppModule. I service precedentemente forniti nell'array providers del modulo root devono spostarsi nell'ApplicationConfig in app.config.ts o usare providedIn: 'root' nel decoratore @Injectable. I service con scope di rotta dovrebbero usare l'array providers nelle configurazioni di rotta.
Quando viene chiesto degli standalone component in un colloquio Angular, concentrarsi su tre punti: (1) la gestione delle dipendenze passa dal livello di modulo a quello di componente, (2) il tree-shaking diventa granulare, e (3) il setup dei test si semplifica perché i componenti dichiarano i propri import.
Fonti
- Angular v22 Release Notes - OnPush di default, selectorless stabile, requisito TypeScript 6
- Angular Standalone Migration Guide - Documentazione ufficiale dello schematic a 3 passaggi
- What's New in Angular 22 - Changelog dettagliato con le modifiche alla strategia di change detection
- Selectorless Components RFC - Motivazioni di design e stato dell'implementazione
Migrazione a standalone: punti chiave per Angular 22
- Lo schematic ufficiale dell'Angular CLI automatizza l'80-90 % della migrazione in tre passaggi sequenziali: convertire le dichiarazioni, rimuovere i moduli, cambiare il bootstrap
- I moduli di routing richiedono una conversione manuale da
loadChildrencon NgModule aloadComponento file di rotte standalone - Gli SharedModule sono l'ostacolo principale: convertire ogni dichiarazione condivisa in standalone individualmente, poi eliminare il modulo
- Abilitare
strictStandalonenel tsconfig per impedire l'introduzione di nuovi NgModule dopo la migrazione - Le dimensioni del bundle calano del 30-55 % grazie al tree-shaking a livello di componente e al lazy loading granulare con
loadComponent - Gli unit test si semplificano con Vitest come runner predefinito: importare il componente standalone direttamente in
TestBedsenza configurazione di moduli - OnPush di default e i selectorless component di Angular 22 si combinano naturalmente con l'architettura standalone per una codebase più pulita e manutenibile
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Sapresti trovare il bug in Angular?
Uno snippet reale, un bug nascosto, un tentativo al giorno. Senza account per provare.

Scritto da
Anthony Fillion-MailletFondatore di SharpSkill
Sviluppatore fullstack da oltre 10 anni. Guida SharpSkill e risponde di tutto ciò che vi viene pubblicato.
Aggiornato il 21 agosto 2026
Tag
Condividi
Articoli correlati

Lavoro come sviluppatore Angular nel 2026: dove cercare, stipendi e preparazione ai colloqui
Il mercato del lavoro per sviluppatori Angular a settembre 2026: offerte attive, statistiche sugli stipendi, opzioni remote e preparazione mirata ai colloqui.

Angular Signals e Computed nel 2026: Reattività Fine-Grained e Domande per Colloqui
Una guida approfondita ad Angular Signals, Computed Signals e al modello reattivo in Angular 20+. Include best practice, interoperabilità con RxJS e domande frequenti nei colloqui tecnici.

Dependency Injection Avanzata in Angular 2026: Provider, Token e Domande da Colloquio
Il sistema di dependency injection di Angular offre un controllo preciso sull'istanziazione dei servizi attraverso provider, token e iniettori gerarchici. Questa analisi approfondita esplora le strategie dei provider, la funzione inject() e le domande tecniche da colloquio.