Migrare un'applicazione da Angular 10 ad Angular 21 significa attraversare undici major release,
ognuna con breaking change propri. Farlo in un solo salto non è mai un'opzione realistica: il team
Angular stesso raccomanda upgrade sequenziali, una major alla volta, usando
ng update ad ogni passaggio. Questa guida copre l'intero percorso: cosa cambia in ogni
versione, i comandi esatti da eseguire, i problemi più comuni incontrati in migrazioni reali, l'impatto
su performance e bundle, e un piano operativo 30/60/90 giorni per gestire il rischio su un progetto
enterprise.
Nota importante: alcuni dettagli di versione (in particolare per Angular 19, 20 e 21,
le più recenti al momento della stesura) vanno sempre verificati contro la documentazione ufficiale
prima di pianificare un upgrade in produzione, con npm view @angular/core versions e
ng version nel progetto reale.
Matrice di Compatibilità
| Angular | TypeScript | RxJS | Node | Angular CLI | Angular Material |
|---|---|---|---|---|---|
| 10 | 3.9 | 6.5 / 6.6 | 10.13 / 12.11 | 10 | 10 |
| 11 | 4.0 | 6.5.3 | 10.13 / 12.11 | 11 | 11 |
| 12 | 4.2 | 6.5.3 / 7 | 12.14 / 14 | 12 | 12 |
| 13 | 4.4 | 6.5.3 / 7 | 12.20 / 14 | 13 | 13 |
| 14 | 4.6 / 4.7 | 6.5.3 / 7 | 14.15 / 16 | 14 | 14 |
| 15 | 4.8 / 4.9 | 6.5.3 / 7 | 14.20 / 16 | 15 | 15 |
| 16 | 4.9 / 5.1 | 6.5.3 / 7 | 16 / 18 | 16 | 16 |
| 17 | 5.2 | 6.5.3 / 7 | 18.13 / 20 | 17 | 17 |
| 18 | 5.4 / 5.5 | 6.5.3 / 7 | 18.19 / 20 / 22 | 18 | 18 |
| 19 | 5.5 / 5.6 | 6.5.3 / 7 | 18.19 / 20 / 22 | 19 | 19 |
| 20 | 5.6+ (verificare) | 7 | 20 / 22 (verificare) | 20 | 20 |
| 21 | da verificare | 7 | da verificare | 21 | 21 |
Per Angular 20 e 21, verifica sempre i requisiti esatti prima di pianificare: npm view
@angular/core@21 peerDependencies restituisce le versioni minime richieste al momento
dell'installazione, più affidabile di qualsiasi tabella statica.
Comandi ng update: il Pattern Sequenziale
# Pattern generale per ogni step (mai saltare una major)
ng update @angular/core@X @angular/cli@X --force
npm install
ng build
npm test
# Esempio concreto: da 10 a 11
ng update @angular/core@11 @angular/cli@11
Il flag --force ignora i controlli di compatibilità dei peer dependency quando una
libreria di terze parti non ha ancora pubblicato il supporto per la nuova major — usalo solo dopo aver
verificato manualmente che la libreria funzioni comunque, mai come default automatico.
--migrate-only esegue solo gli schematics di migrazione senza toccare le versioni dei
pacchetti (utile per rieseguire una migrazione specifica dopo un fix manuale), mentre
--from/--to permettono di puntare a un intervallo di versioni preciso
quando ng update non rileva automaticamente la versione di partenza corretta.
Angular 11 → 12: da View Engine a Ivy Everywhere
Panoramica e Breaking Change
- Angular 11 (novembre 2020): TypeScript 4.0, stack trace più leggibili, hot module replacement opzionale.
- Angular 12 (maggio 2021): Ivy diventa il compilatore/runtime di default anche per la pubblicazione di librerie (Angular Package Format aggiornato), strict mode abilitato di default sui nuovi progetti, deprecazione formale di View Engine.
- API deprecate:
entryComponentsnon più necessario con Ivy,relativeLinkResolutionnel router deprecato.
ng update @angular/core@12 @angular/cli@12
// tsconfig.json — strict mode raccomandato da Angular 12 in poi
{
"compilerOptions": { "strict": true },
"angularCompilerOptions": { "strictTemplates": true }
}
Angular 13: Addio a View Engine e IE11
Panoramica e Breaking Change
- View Engine completamente rimosso: solo Ivy da questa versione in poi,
ngccnon più necessario per le librerie pubblicate col nuovo Angular Package Format. - Supporto a Internet Explorer 11 rimosso — se il tuo pubblico include ancora IE11, questa è una major da valutare con attenzione.
- Nuova API per i dynamic component (
ViewContainerRef.createComponentsenzaComponentFactoryResolver).
// Prima (Angular 12 e precedenti)
constructor(private resolver: ComponentFactoryResolver) {}
createDynamic() {
const factory = this.resolver.resolveComponentFactory(MyComponent);
this.container.createComponent(factory);
}
// Dopo (Angular 13+) — nessun ComponentFactoryResolver necessario
createDynamic() {
this.container.createComponent(MyComponent);
}
Angular 14: Standalone Components in Developer Preview
Panoramica e Breaking Change
- Standalone components/direttive/pipe introdotti (developer preview, non ancora raccomandati in produzione).
- Typed Reactive Forms:
FormControle affini diventano generici, con conseguenti errori di tipo su codice esistente che assumevaany. - Extended diagnostics del compilatore segnalano pattern rischiosi nei template già in fase di build.
ng update @angular/core@14 @angular/cli@14
// Typed forms — il compilatore ora rileva errori prima invisibili
const form = new FormGroup({ email: new FormControl('', { nonNullable: true }) });
// form.value.email è ora tipizzato string, non any
Angular 15: Standalone Stabili e NgOptimizedImage
Panoramica e Breaking Change
- Standalone API diventa stabile e raccomandata per nuovi progetti.
- Direttiva
NgOptimizedImagestabile: lazy loading e dimensionamento immagini automatici con impatto diretto su LCP. - Angular Material passa alla nuova architettura basata su MDC (Material Design Components), con possibili differenze visive minori sui componenti esistenti.
// Componente standalone — nessun NgModule richiesto
@Component({ selector: 'app-widget', standalone: true, imports: [CommonModule], template: `...` })
export class WidgetComponent {}
<img ngSrc="hero.jpg" width="800" height="400" priority />
Angular 16: Signals in Developer Preview
Panoramica e Breaking Change
- Signals introdotti in developer preview: reattività granulare alternativa/complementare a Zone.js.
- Builder esbuild in developer preview per
ng build: tempi di build significativamente più rapidi. - Required inputs (
@Input({ required: true })) e self-closing tags nei template (<app-widget />). - Supporto sperimentale a Jest come test runner alternativo a Karma.
ng update @angular/core@16 @angular/cli@16
# Opt-in al builder esbuild (developer preview in questa versione)
// Signal — reattività granulare senza dipendere dal digest di Zone.js
count = signal(0);
doubled = computed(() => this.count() * 2);
Angular 17: Nuovo Control Flow e Vite/esbuild di Default
Panoramica e Breaking Change
- Nuova sintassi di control flow nei template (
@if,@for,@switch) stabile, sostituisce*ngIf/*ngForcon performance di rendering migliori. - Builder basato su esbuild/Vite diventa il default per i nuovi progetti (dev server drasticamente più veloce).
@deferper il lazy loading dichiarativo di blocchi di template (deferrable views).
<!-- Prima: *ngFor/*ngIf -->
<div *ngIf="user">{{ user.name }}</div>
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
<!-- Dopo: nuovo control flow, track obbligatorio in @for -->
@if (user) { <div>{{ user.name }}</div> }
@for (item of items; track item.id) { <li>{{ item.name }}</li> }
<!-- @defer — carica il blocco solo quando entra in viewport -->
@defer (on viewport) {
<heavy-chart />
} @placeholder {
<div class="skeleton"></div>
}
Angular 18: Zoneless Sperimentale e Material 3
Panoramica e Breaking Change
- Change detection zoneless sperimentale: possibilità di rimuovere Zone.js dal bundle per applicazioni basate su Signal.
- Angular Material 3 (Material Design 3) stabile, con nuovi design token di theming.
- Event replay per applicazioni con SSR/hydration: gli eventi utente durante l'hydration non vengono più persi.
// main.ts — opt-in sperimentale a zoneless (rimuove la dipendenza da Zone.js)
bootstrapApplication(AppComponent, {
providers: [provideExperimentalZonelessChangeDetection()],
});
Angular 19: Standalone di Default e Incremental Hydration
Panoramica e Breaking Change
- I nuovi progetti generati da
ng newsono standalone by default:NgModulenon è più lo scaffold predefinito. - Incremental hydration: idratazione selettiva di parti della pagina invece dell'intera applicazione in blocco, con benefici diretti su Time to Interactive.
- Nuove primitive Signal-based (
linkedSignal,resource) per stato derivato e data fetching reattivo.
ng update @angular/core@19 @angular/cli@19
// resource() — data fetching reattivo basato su Signal (verificare API esatta nella versione installata)
userResource = resource({
request: () => this.userId(),
loader: ({ request }) => fetchUser(request),
});
Angular 20 e 21: Verifica Sempre la Documentazione Ufficiale
Per queste due release, più recenti al momento della stesura di questa guida, i dettagli esatti di breaking change e requisiti di dipendenze vanno sempre confermati con i comandi ufficiali prima di pianificare l'upgrade — la direzione generale è consolidamento di Signals e zoneless verso la stabilità piena, ulteriore riduzione del bundle di default, e continua evoluzione del builder esbuild/Vite, ma le date esatte e i dettagli specifici delle API vanno verificati caso per caso.
# Verifica sempre versione corrente, changelog e requisiti prima di procedere
npm view @angular/core versions --json | tail -20
ng version
npx ng update @angular/core@21 @angular/cli@21 --dry-run
RxJS: da rxjs-compat alla Rimozione
Nei progetti partiti da Angular 10, è comune trovare ancora rxjs-compat installato per
supportare la sintassi con operatori concatenati pre-pipeable. Va rimosso il prima possibile: oltre a
gonfiare il bundle, nasconde deprecazioni che altrimenti il compilatore segnalerebbe subito.
// Prima — operatori concatenati (richiede rxjs-compat)
source.map(x => x * 2).filter(x => x > 10).subscribe();
// Dopo — pipeable operators, nessuna dipendenza da rxjs-compat
source.pipe(map(x => x * 2), filter(x => x > 10)).subscribe();
npm uninstall rxjs-compat
Testing: da Karma/Protractor a Jest/Cypress
Protractor è deprecato dal team Angular stesso dal 2022 e va sostituito indipendentemente dalla versione di Angular target. Karma resta funzionante più a lungo, ma Jest (supportato sperimentalmente da Angular 16 in poi) offre tempi di esecuzione significativamente più rapidi in CI.
# Migrazione E2E: rimuovi Protractor, installa Cypress
ng g @angular/cli:e2e-e2e-schematic-removal 2>/dev/null || echo "rimuovi manualmente e2e/ e protractor.conf.js"
npm install --save-dev cypress
npx cypress open
// Test aggiornato — Jest invece di Jasmine/Karma
describe('UserCardComponent', () => {
it('mostra il nome utente', () => {
const fixture = TestBed.createComponent(UserCardComponent);
fixture.componentRef.setInput('user', { name: 'Mario' });
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('Mario');
});
});
Script di Automazione per Upgrade Sequenziali
#!/bin/bash
# upgrade-sequenziale.sh — esegue ng update versione per versione con report
set -e
VERSIONS=(11 12 13 14 15 16 17 18 19)
for v in "${VERSIONS[@]}"; do
echo "=== Upgrade a Angular $v ==="
ng update @angular/core@$v @angular/cli@$v --force
npm install
npm run build > "report-build-v$v.log" 2>&1 || { echo "❌ Build fallita su v$v"; exit 1; }
npm test -- --watch=false > "report-test-v$v.log" 2>&1 || echo "⚠️ Test falliti su v$v, controllare report-test-v$v.log"
git add -A && git commit -m "chore: upgrade Angular a v$v"
done
echo "✅ Upgrade sequenziale completato fino a v${VERSIONS[-1]}"
Monorepo: Nx, Lerna e pnpm Workspace
In un monorepo con librerie interne condivise, l'ordine di aggiornamento conta: aggiorna prima le
librerie condivise (verificando che ogni peerDependencies dichiari un range compatibile
con la nuova major), ricompilale, poi aggiorna le applicazioni consumer. Con Nx, nx migrate
latest orchestran automaticamente questo ordine per i pacchetti gestiti dal workspace.
# Nx — migrazione orchestrata dell'intero workspace
npx nx migrate latest
npx nx migrate --run-migrations
CI/CD: Aggiornare le Pipeline
# GitHub Actions — matrice Node/Angular coerente con la versione target
jobs:
build:
strategy:
matrix:
node-version: [20.x]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build -- --configuration production
- run: npm test -- --watch=false --browsers=ChromeHeadless
Aggiorna sempre la versione di Node nella pipeline prima di eseguire ng update
localmente: un mismatch tra Node locale e Node CI è una causa frequente di "funziona sul mio computer"
durante gli upgrade.
Impatto su Performance e Bundle
- Ivy vs View Engine: bundle mediamente più piccoli e tree-shaking più efficace già dalla transizione a Ivy (Angular 9-13).
- Rimozione di ngcc: build più veloci dopo Angular 13, quando tutte le librerie sono pubblicate in Angular Package Format nativo Ivy.
- esbuild/Vite: riduzione drastica dei tempi di build e del dev server rispetto al builder Webpack legacy, a partire da Angular 16-17.
- Standalone components: eliminano il boilerplate degli
NgModulee migliorano il tree-shaking a grana fine per-componente. - Signals e zoneless: potenziale rimozione di Zone.js dal bundle finale, con riduzione di dimensione e cicli di change detection non necessari.
- Differential loading: rimosso nelle versioni più recenti perché il supporto browser moderno ha reso obsoleta la necessità di bundle separati ES5/ES2015+.
Problemi Noti e Workaround Comuni
- Errori di tipo dopo l'attivazione di strict mode: attivalo gradualmente file per file con
// @ts-strict-ignoretemporaneo invece di bloccare l'intero upgrade. - Librerie di terze parti non aggiornate: verifica prima con
npm outdatede valuta fork temporanei o patch viapatch-packagese la libreria è abbandonata. - Peer dependency conflict con
--force: documenta sempre perché è stato necessario, per non perdere traccia di un debito tecnico nascosto. @forsenzatrack: il nuovo control flow richiedetrackobbligatorio, un errore di build comune durante la migrazione da*ngFor.
Checklist Pre-Upgrade
- Branch dedicato per l'upgrade, mai direttamente su main.
- Copertura test esistente misurata come baseline prima di iniziare.
- Issue tracker pulito: nessun bug noto già aperto che possa confondersi con una regressione dell'upgrade.
- Backup/tag Git della versione funzionante corrente, pronto per un rollback immediato.
Checklist di Rollback
- Tag Git della versione pre-upgrade sempre creato prima di iniziare (
git tag pre-upgrade-v10). package-lock.jsoncommittato ad ogni step, per poter ripristinare l'esatto grafo di dipendenze precedente.- Pipeline CI/CD in grado di eseguire il deploy della versione taggata precedente senza modifiche manuali.
- Se il progetto ha migrazioni di dati collegate a una feature della nuova versione, verifica che siano reversibili prima del deploy.
Piano Operativo 30/60/90 Giorni
Giorni 1-30: Angular 10 → 14
- Upgrade sequenziale 10→11→12→13→14 su branch dedicato — KPI: build verde ad ogni step, 0 regressioni funzionali note.
- Rimozione di
rxjs-compate ViewEngine residuo — KPI: 0 warning di deprecazione in build.
Giorni 31-60: Angular 15 → 18
- Adozione standalone components sui moduli nuovi, migrazione E2E a Cypress — KPI: 0 test Protractor residui.
- Upgrade sequenziale 15→16→17→18, adozione nuovo control flow nei template più visitati — KPI: tempo di build ridotto misurato prima/dopo.
Giorni 61-90: Angular 19 → 21
- Upgrade finale fino alla versione target, verificata contro documentazione ufficiale ad ogni step — KPI: build e test pass rate al 100% sulla versione finale.
- Valutazione zoneless/Signals sui componenti a più alto traffico — KPI: bundle size finale confrontato con la baseline pre-upgrade.
Case Study 1: App Enterprise con Monorepo Nx
Un'applicazione enterprise su monorepo Nx (8 librerie interne condivise, Angular 10) ha completato
l'upgrade fino ad Angular 18 in 14 settimane con 1.5 sviluppatori dedicati (~420 ore/persona
totali). Problemi principali: 3 librerie di terze parti senza supporto Ivy nativo, risolte con
ngcc forzato fino ad Angular 12 e successiva sostituzione con alternative mantenute.
Risultato: bundle iniziale ridotto del 28% (standalone components + esbuild), tempo di build CI
ridotto da 9 a 3 minuti.
Case Study 2: E-commerce con Angular Material
Un e-commerce con forte dipendenza da Angular Material (Angular 11) ha completato l'upgrade fino ad
Angular 17 in 8 settimane con 2 sviluppatori part-time (~180 ore/persona). La transizione a Material 3
(MDC-based) ha richiesto un audit visivo completo dei componenti custom-styled, il problema più
costoso dell'intera migrazione. Risultato: 12 bug di layout latenti risolti durante l'audit,
Time to Interactive migliorato del 22% grazie al nuovo control flow e a @defer sui
widget non critici above-the-fold.
FAQ
Si può saltare una major version durante l'upgrade?
Non è raccomandato: ng update applica schematics di migrazione specifici per ogni major, saltarne una rischia trasformazioni di codice incomplete.
Quanto tempo richiede in media un upgrade da Angular 10 a 21?
Dipende fortemente dalla dimensione del progetto e dalle librerie di terze parti coinvolte; progetti enterprise reali richiedono tipicamente da 2 a 4 mesi con effort parziale dedicato.
Serve riscrivere tutti i componenti in standalone?
No, standalone e NgModule possono coesistere a lungo; la migrazione può essere gradita e opportunistica sui moduli toccati per altri motivi.
Cosa fare se una libreria di terze parti non supporta la nuova major?
Verifica alternative mantenute, valuta un fork temporaneo con patch minime, o isola la libreria dietro un adapter per sostituirla più facilmente in futuro.
ng update --force è sicuro?
Solo dopo aver verificato manualmente che le librerie interessate funzionino comunque; non è un flag da usare come default automatico.
Zone.js va rimosso subito?
No, zoneless è ancora in evoluzione nelle versioni più recenti; valuta la rimozione solo dopo un audit completo dei componenti che dipendono implicitamente dal digest automatico.
Come si verificano vulnerabilità di sicurezza durante l'upgrade?
Con npm audit ad ogni step e, per progetti enterprise, uno scanner dedicato come Snyk integrato in CI.
Serve migrare da Karma a Jest per forza?
No, Karma resta supportato più a lungo; Jest è un'opzione per velocizzare la CI, non un requisito obbligatorio delle nuove major.
Cosa succede ai test Protractor esistenti?
Vanno sostituiti con Cypress o Playwright, indipendentemente dalla versione target, perché Protractor è deprecato dal team Angular stesso.
Come si stima l'effort di un upgrade multi-major?
Sommando l'effort per ogni major (build, fix breaking change, test) più un buffer per le librerie di terze parti non aggiornate, tipicamente il rischio maggiore.
È necessario aggiornare Node ad ogni major Angular?
Non sempre ad ogni singola major, ma va verificato nella matrice di compatibilità: alcune major alzano il requisito minimo di Node.
Conviene fare l'upgrade in un unico grande PR o in tanti piccoli?
Tanti piccoli PR, uno per major version, per isolare il rischio e facilitare il rollback di un singolo step in caso di regressione.
Errori Comuni da Evitare
- Saltare major version per "fare prima": gli schematics di migrazione sono pensati per essere applicati in sequenza, saltarne una causa trasformazioni incomplete.
- Non leggere il changelog di ogni major: alcune breaking change non hanno uno schematic automatico e richiedono intervento manuale.
- Ignorare i warning di deprecazione: diventano errori bloccanti nella major successiva, meglio risolverli quando sono ancora solo warning.
--forceusato senza verifica: nasconde incompatibilità reali che emergono più tardi in produzione.- Non aggiornare Node in CI prima del codice locale: causa build che funzionano solo su una macchina.
- Rimandare la rimozione di rxjs-compat: nasconde deprecazioni RxJS che il compilatore altrimenti segnalerebbe.
- Nessun tag Git prima di iniziare l'upgrade: rende il rollback molto più lento e rischioso in caso di regressione.
- Test E2E su Protractor mantenuti "per ora": Protractor è deprecato, ogni mese di ritardo nella migrazione aumenta il debito tecnico.
- Attivare strict mode TypeScript su tutto il progetto in un colpo solo: genera centinaia di errori simultanei, meglio modulo per modulo.
- Non misurare il bundle size prima/dopo: senza una baseline, non è possibile verificare se l'upgrade ha davvero portato i benefici di performance attesi.
- Aggiornare le librerie interne del monorepo dopo le app consumer: causa incompatibilità temporanee, l'ordine corretto è sempre librerie prima, app dopo.
- Nessun piano di rollback per le pipeline CI/CD: un upgrade che rompe la build in produzione senza un percorso di rollback rapido trasforma un problema tecnico in un incidente.
Come Verificare
- Verifica versione corrente e disponibili:
ng versionenpm view @angular/core versions. - Esegui un dry-run prima di ogni update reale:
ng update @angular/core@X --dry-run. - Controlla vulnerabilità dopo ogni step:
npm audit. - Verifica la build di produzione:
ng build --configuration production. - Esegui l'intera suite di test:
npm test -- --watch=falsee la suite E2E Cypress. - Misura il bundle size prima/dopo con
npx webpack-bundle-analyzero l'output di build di Angular CLI.
Conclusione
Un upgrade da Angular 10 a 21 non è un singolo evento, ma un programma pluri-mensile con undici tappe
sequenziali, ognuna con il proprio rischio e i propri benefici. Il pattern che funziona in pratica è
sempre lo stesso: una major alla volta, ng update seguito da build e test verdi prima di
procedere, tag Git ad ogni step per un rollback rapido, e una verifica sistematica della
documentazione ufficiale per le versioni più recenti dove i dettagli non sono ancora consolidati nella
memoria collettiva del team.
Vuoi un piano di migrazione dettagliato per il tuo progetto specifico o una valutazione dell'effort richiesto? Richiedi un audit tecnico: in poche ore di analisi della codebase è possibile stimare tempi, rischi principali e librerie di terze parti da monitorare durante l'upgrade.