<link rel="stylesheet" href="/assets/fonts/jetbrains-mono/jetbrains-mono.css" />
All posts

Angular Migration from v10 to v21: The complete version-by-version Guide

Migrating an application from Angular 10 to Angular 21 means going through eleven major releases, each with its own breaking changes. Doing it in one leap is never a realistic option: the team Angular itself recommends sequential upgrades, one major at a time, using ng update at each pass. This guide covers the entire journey: what changes in each version, the exact commands to run, the most common problems encountered in real migrations, the impact on performance and bundles, and a 30/60/90 day operational plan to manage risk on a project enterprise.

Important note: Some version details (especially for Angular 19, 20 and 21, the most recent at the time of writing) must always be checked against the official documentation before planning an upgrade in production, with npm view @angular/core versions and ng version in real project.

Compatibility Matrix

AngularTypeScriptRxJSNodeAngular CLIAngular Material
103.96.5 / 6.610.13 / 12.111010
114.06.5.310.13 / 12.111111
124.26.5.3 / 712.14 / 141212
134.46.5.3 / 712.20 / 141313
144.6 / 4.76.5.3 / 714.15 / 161414
154.8 / 4.96.5.3 / 714.20 / 161515
164.9 / 5.16.5.3 / 716 / 181616
175.26.5.3 / 718.13 / 201717
185.4 / 5.56.5.3 / 718.19 / 20 / 221818
195.5 / 5.66.5.3 / 718.19 / 20 / 221919
205.6+ (check)720 / 22 (check)2020
21to check7from verify2121

For Angular 20 and 21, always check the exact requirements before planning: npm view @angular/core@21 peerDependencies returns the minimum versions currently required of installation, more reliable than any static table.

ng update commands: the Sequential Pattern

# 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

The --force flag bypasses peer dependency compatibility checks when a third-party library has not yet released support for the new major — only use it once you have manually checked that the library works anyway, never as an automatic default. --migrate-only executes only the migration schematics without touching the versions of the packages (useful for rerunning a specific migration after a manual fix), while --from/--to allow you to target a specific version range when ng update does not automatically detect the correct starting version.

Angular 11 → 12: From View Engine to Ivy Everywhere

Overview and Breaking Change

  • Angular 11 (November 2020): TypeScript 4.0, more readable stack traces, optional hot module replacement.
  • Angular 12 (May 2021): Ivy becomes the default compiler/runtime also for publishing libraries (Angular Package Format updated), strict mode enabled by default on new projects, formal deprecation of View Engine.
  • Deprecated APIs: entryComponents no longer needed with Ivy, relativeLinkResolution in router deprecated.
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: Goodbye to View Engine and IE11

Overview and Breaking Change

  • View Engine completely removed: only Ivy from this version onwards, ngcc no longer needed for libraries published with the new Angular Package Format.
  • Internet Explorer 11 support removed — if your audience still includes IE11, this is a major to evaluate carefully.
  • New API for dynamic components (ViewContainerRef.createComponent without ComponentFactoryResolver).
// 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

Overview and Breaking Change

  • Standalone components/directives/pipes introduced (developer preview, not yet recommended for production).
  • Typed Reactive Forms: FormControl and similar become generic, resulting in type errors on existing code that assumed any.
  • Extended compiler diagnostics report risky patterns in templates already in the build phase.
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 Stable and NgOptimizedImage

Overview and Breaking Change

  • Standalone API becomes stable and recommended for new projects.
  • Directive NgOptimizedImage stable: automatic lazy loading and image sizing with direct impact on LCP.
  • Angular Material switches to the new architecture based on MDC (Material Design Components), with possible minor visual differences on existing components.
// 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

Overview and Breaking Change

  • Signals introduced in developer preview: alternative/complementary granular reactivity to Zone.js.
  • Builder esbuild in developer preview for ng build: Significantly faster build times.
  • Required inputs (@Input({ required: true })) and self-closing tags in templates ().
  • Experimental support for Jest as an alternative test runner to 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: New Control Flow and Default Vite/esbuild

Overview and Breaking Change

  • New control flow syntax in templates (@if, @for, @switch) stable, replaces *ngIf/*ngFor with better rendering performance.
  • Builder based on esbuild/Vite becomes default for new projects (drastically faster server dev).
  • @defer for declarative lazy loading of template blocks (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 Experimental and Material 3

Overview and Breaking Change

  • Experimental zoneless change detection: Ability to remove Zone.js from bundle for Signal-based applications.
  • Angular Material 3 (Material Design 3) stable, with new theming token designs.
  • Event replay for applications with SSR/hydration: user events during hydration are no longer lost.
// main.ts — opt-in sperimentale a zoneless (rimuove la dipendenza da Zone.js)
bootstrapApplication(AppComponent, {
  providers: [provideExperimentalZonelessChangeDetection()],
});

Angular 19: Standalone by Default and Incremental Hydration

Overview and Breaking Change

  • New projects generated by ng new are standalone by default: NgModule is no longer the default scaffold.
  • Incremental hydration: selective hydration of parts of the page instead of the entire application in bulk, with direct benefits on Time to Interactive.
  • New Signal-based primitives (linkedSignal, resource) for derived state and reactive data fetching.
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 and 21: Always Check the Official Documentation

For these two releases, most recent at the time of writing this guide, the exact details of breaking change and dependency requirements must always be confirmed with the official commands before plan the upgrade — the general direction is Signals consolidation and zoneless towards the full stability, further reduction of the default bundle, and continuous evolution of the builder esbuild/Vite, but exact dates and specific API details should be verified on a case-by-case basis.

# 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: from rxjs-compat to Removal

In projects started from Angular 10, it is common to still find rxjs-compat installed for support syntax with pre-pipeable concatenated operators. It should be removed as soon as possible: in addition to bloating the bundle hides deprecations that the compiler would otherwise report immediately.

// 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: from Karma/Protractor to Jest/Cypress

Protractor has been deprecated by the Angular team itself since 2022 and should be replaced regardless Angular target version. Karma remains functional longer, but Jest (experimentally supported Angular 16 onwards) offers significantly faster execution times 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');
  });
});

Automation Scripts for Sequential Upgrades

#!/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 and pnpm Workspace

In a monorepo with shared internal libraries, the update order matters: update first shared libraries (verifying that each peerDependencies declares a compatible range with the new major), recompile them, then update the consumer applications. With Nx, nx migrate latest automatically orchestrates this order for packages managed by the workspace.

# Nx — migrazione orchestrata dell'intero workspace
npx nx migrate latest
npx nx migrate --run-migrations

CI/CD: Updating Pipelines

# 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

Always update the Node version in the pipeline before running ng update locally: a mismatch between local Node and CI Node is a frequent cause of "works on my computer" during upgrades.

Impact on Performance and Bundle

  • Ivy vs View Engine: smaller bundles on average and more effective tree-shaking already since the transition to Ivy (Angular 9-13).
  • Removing ngcc: Faster builds after Angular 13, when all libraries are published in native Angular Package Format Ivy.
  • esbuild/Vite: Dramatically reduce build and dev server times compared to legacy Webpack builder, starting in Angular 16-17.
  • Standalone components: eliminate the boilerplate of NgModules and improve per-component fine-grained tree-shaking.
  • Signals and zoneless: Potential removal of Zone.js from final bundle, resulting in size reduction and unnecessary change detection loops.
  • Differential loading: Removed in newer versions because modern browser support has obsoleted the need for separate ES5/ES2015+ bundles.

Known Issues and Common Workarounds

  • Type errors after enabling strict mode: enable it gradually file by file with temporary // @ts-strict-ignore instead of blocking the entire upgrade.
  • Outdated third-party libraries: Check first with npm outdated and consider temporary forks or patches via patch-package if the library is abandoned.
  • Peer dependency conflict with --force: Always document why it was necessary, so as not to lose track of hidden technical debt.
  • @for without track: new control flow requires track mandatory, a common build error when migrating from *ngFor.

Pre-Upgrade Checklist

  • Dedicated branch for upgrade, never directly on main.
  • Existing test coverage measured as a baseline before starting.
  • Clean issue tracker: no known bugs already open that could be confused with an upgrade regression.
  • Backup/Git tag of the current working version, ready for immediate rollback.

Rollback Checklist

  • Pre-upgrade version Git tag always created before starting (git tag pre-upgrade-v10).
  • package-lock.json committed at each step, in order to restore the exact previous dependency graph.
  • CI/CD pipeline capable of deploying the previous tagged version without manual changes.
  • If the project has data migrations linked to a feature in the new version, verify that they are reversible before deploying.

30/60/90 Day Operational Plan

Days 1-30: Angular 10 → 14

  • Sequential upgrade 10→11→12→13→14 on dedicated branch — KPI: green build at each step, 0 known functional regressions.
  • Removal of rxjs-compat and residual ViewEngine — KPI: 0 deprecation warnings in build.

Days 31-60: Angular 15 → 18

  • Adoption of standalone components on new modules, E2E migration to Cypress — KPI: 0 residual Protractor tests.
  • Sequential upgrade 15→16→17→18, adoption of new control flow in the most visited templates — KPI: reduced build time measured before/after.

Days 61-90: Angular 19 → 21

  • Final upgrade up to the target version, verified against official documentation at each step — KPI: build and test pass rate at 100% on the final version.
  • Zoneless/Signals evaluation on the highest traffic components — KPI: final bundle size compared to the pre-upgrade baseline.

Case Study 1: Enterprise App with Monorepo Nx

An enterprise application on monorepo Nx (8 shared internal libraries, Angular 10) has completed upgrading to Angular 18 in 14 weeks with 1.5 dedicated developers (~420 person hours totals). Top issues: 3 3rd party libraries without native Ivy support, fixed with ngcc forced up to Angular 12 and subsequent replacement with retained alternatives. Result: initial bundle reduced by 28% (standalone components + esbuild), CI build time reduced from 9 to 3 minutes.

Case Study 2: E-commerce with Angular Material

An e-commerce site with heavy dependency on Angular Material (Angular 11) has completed the upgrade to Angular 17 in 8 weeks with 2 part-time developers (~180 person hours). The transition to Material 3 (MDC-based) required a complete visual audit of custom-styled components, the most problem expensive of the entire migration. Result: 12 latent layout bugs fixed during the audit, Time to Interactive improved by 22% thanks to the new control flow and @defer on non-critical above-the-fold widgets.

FAQ

Can you skip a major version when upgrading?

Not recommended: ng update apply migration schematics specific to each major, skipping one risks incomplete code transformations.

How long does an upgrade from Angular 10 to 21 take on average?

Depends heavily on the size of the project and the third-party libraries involved; Real enterprise projects typically require 2 to 4 months with partial dedicated effort.

Do you need to rewrite all standalone components?

No, standalone and NgModule can coexist for a long time; migration may be welcome and opportunistic on modules touched for other reasons.

What to do if a third-party library does not support the new major?

Check maintained alternatives, consider a temporary fork with minimal patches, or isolate the library behind an adapter to more easily replace it in the future.

Is

ng update --force safe?

Only after manually verifying that the affected libraries still work; it is not a flag to be used as an automatic default.

Should Zone.js be removed immediately?

No, zoneless is still evolving in newer versions; consider removal only after a complete audit of components that implicitly depend on the automatic digest.

How do security vulnerabilities occur during upgrade?

With npm audit at each step and, for enterprise projects, a dedicated scanner like Snyk integrated into CI.

Do you really need to migrate from Karma to Jest?

No, Karma remains supported longer; Jest is an option for speeding up CI, not a mandatory requirement of new majors.

What happens to existing Protractor tests?

They should be replaced with Cypress or Playwright, regardless of the target version, because Protractor is deprecated by the Angular team itself.

How do you estimate the effort of a multi-major upgrade?

Summing the effort for each major (build, fix breaking change, test) plus a buffer for non-updated third-party libraries, typically the greatest risk.

Is it necessary to update Node with every Angular major?

Not always for every single major, but it must be checked in the compatibility matrix: some majors raise the minimum requirement of Node.

Is it better to upgrade in one large PR or in many small ones?

Many small PRs, one for major version, to isolate the risk and facilitate the rollback of a single step in case of regression.

Common Mistakes to Avoid

  • Skip major version to "do it first": migration schematics are meant to be applied sequentially, skipping one causes incomplete transformations.
  • Do not read the changelog of each major: some breaking changes do not have an automatic schematic and require manual intervention.
  • Ignore deprecation warnings: they become blocking errors in the next major, better to resolve them when they are still just warnings.
  • --force used without verification: Hides real incompatibilities that emerge later in production.
  • Do not upgrade Node in CI before locale code - causes builds that only work on one machine.
  • Delay removal of rxjs-compat: Hide RxJS deprecations that the compiler would otherwise report.
  • No Git tags before starting the upgrade: Makes rollback much slower and riskier in case of regression.
  • E2E tests on Protractor kept "for now": Protractor is deprecated, every month of migration delay increases technical debt.
  • Enable TypeScript strict mode on the entire project in one fell swoop: generate hundreds of simultaneous errors, preferably module by module.
  • Do not measure the bundle size before/after: without a baseline, it is not possible to verify whether the upgrade really brought the expected performance benefits.
  • Update internal monorepo libraries after consumer apps: causes temporary incompatibilities, correct order is always libraries first, apps later.
  • No rollback plan for CI/CD pipelines: An upgrade that breaks the production build without a quick rollback path turns a technical issue into an incident.

How to Check

  • Check current version and available: ng version and npm view @angular/core versions.
  • Perform a dry-run before every real update: ng update @angular/core@X --dry-run.
  • Check vulnerabilities after each step: npm audit.
  • Check the production build: ng build --configuration production.
  • Run the entire test suite: npm test -- --watch=false and the E2E Cypress suite.
  • Measure bundle size before/after with npx webpack-bundle-analyzer or Angular CLI build output.

Conclusion

An upgrade from Angular 10 to 21 is not a single event, but a multi-month program with eleven stages sequential, each with its own risks and benefits. The pattern that works in practice is always the same: one major at a time, ng update followed by green builds and tests before proceed, Git tags at each step for quick rollback, and systematic verification of official documentation for newer versions where details are not yet consolidated into the collective memory of the team.

Do you want a detailed migration plan for your specific project or an assessment of the effort required? Request a technical audit: in a few hours of codebase analysis it is You can estimate times, key risks, and third-party libraries to monitor during the upgrade.

💬 Reader notes

0 notes

Write a note

Share your opinion, a suggestion or a compliment

Latest notes

No notes yet. Be the first to comment!