Nov 27, 2025 · Updated Jun 8, 2026

Angular Migration Path: Full Upgrade Guide from v15 to v21 [Step-by-Step, 2026]

angularangular20migrationarchitecturestandalonesignals

Step-by-step checklists for upgrading Angular v15 to v21. Covers standalone migration, Signals adoption, Karma removal, and per-version breaking changes.

Part 1: The Upgrade Itself

First, let’s get the basics out of the way. Before running any commands, make sure your environment is ready.

Prerequisites

  • Node.js: v20.11.1 or later.
  • TypeScript: v5.8 or later.
  • Project Backup: Make sure you commit all your current changes in Git. Seriously.

The Update Command

Once you’ve confirmed your Node.js version, run the command that fits your project.

For a standard Angular project:

ng update @angular/cli @angular/core

If you use Angular Material:

ng update @angular/cli @angular/core @angular/material

The update process will run, but you’re likely to hit your first roadblock immediately.

Part 2: What Breaks Immediately 🛑

Unlike previous updates, Angular 20 introduces a significant breaking change that will stop your build.

1. The Full Story Behind Karma’s Removal

The first thing you’ll notice is that ng test will fail. This isn’t just a bug; it’s a fundamental change in Angular’s build tools.

With Angular 20, the default build package changes from @angular-devkit/build-angular to the new @angular/build. This new package does not include Karma. The web ecosystem has moved on to faster, more modern test runners like Vitest and Jest, and Karma had become a bottleneck.

The Temporary Fix: To get your tests running without migrating everything today, you need to manually reinstall the old build tool. This forces the CLI to use the old compiler that still supports Karma.

npm install @angular-devkit/build-angular --save-dev

This is a compatibility bridge. The message from the Angular team is clear: start planning your migration to Jest or Vitest soon.

2. browserslist and Browser Support

Here’s a smaller detail that might catch you by surprise. Angular 20 officially no longer supports Opera. If you have “Opera” listed in your .browserslistrc file, your build may fail or throw warnings. Remove it to resolve the issue.

Part 3: The New Architecture 🏛️

Beyond the breaking changes, Angular 20 pushes forward a more modern, explicit, and scalable architecture.

1. Standalone is the Default

New projects generated with ng new are now standalone by default. This marks a fundamental architectural shift away from NgModules. By listing dependencies directly in a component’s imports array, each component becomes self-contained.

This change:

  • Clarifies your architecture: You know exactly what each component needs.
  • Improves tree-shaking: Leads to smaller, more optimized bundles.

To migrate your existing projects, you can run the standalone migration schematic:

ng generate @angular/core:standalone

2. A Folder Structure That Tells a Story

A good folder structure doesn’t just hold files—it tells you what the application does. It’s worth mentioning the official Angular style guide, as its recommendations are built on years of community experience. This philosophy, detailed on their file structure reference page, is often summarized by the acronym LIFT:

  • Locate your code easily.
  • Identify what a file does at a glance.
  • Flat structure for as long as you can.
  • Try to be DRY (Don’t Repeat Yourself).

The main takeaway is to organize by feature, not by type. Instead of a components folder and a services folder, you create folders for features like user-profile or product-list.

Let’s use a practical example for an e-commerce app:

src/app/ ├── core/ │ ├── auth/ # Authentication logic used everywhere │ │ ├── auth-store.ts │ │ └── auth-interceptor.ts │ └── layout/ # The app's shell: navbar, footer │ ├── navbar.ts │ └── footer.ts ├── features/ │ ├── products/ # Everything for browsing products │ │ ├── product-list.ts │ │ ├── product-details.ts │ │ └── product-search.ts │ └── cart/ # The shopping cart feature │ ├── cart-store.ts │ ├── cart-view.ts │ └── add-to-cart.ts └── shared/ # Reusable "dumb" components & utilities └── ui/ ├── button.ts ├── spinner.ts └── price.pipe.ts

Why This Works:

  1. core 🏛️ (Provide once): Services and components the app needs to run, loaded only once (AuthStore, Navbar).
  2. features (What your app does): The heart of your application. Each folder is a self-contained feature.
  3. shared ♻️ (Reusable building blocks): “Dumb” components, pipes, and directives that don’t know anything about the features they’re used in (Button, Spinner). They are imported by feature modules.

3. The New Naming Convention

Angular 20 introduces a new official naming convention that drops traditional suffixes like .component.ts or .service.ts.

The goal is to focus on the file’s intent rather than its technical type. A class that handles state is a “store,” and one that makes HTTP requests is an “api.” This makes your codebase’s purpose much clearer, especially in a feature-based folder structure.

Part 4: The New Tools & Syntax 🚀

Finally, let’s look at the new tools that will make your day-to-day development better.

1. Control Flow is More Than Syntactic Sugar

The new @for block replaces *ngFor and is a major improvement.

Old Syntax:

<div *ngFor="let item of items; trackBy: trackItemById"> {{ item.name }} </div>

New Syntax:

@for (item of items; track item.id) { <div>{{ item.name }}</div> } @empty { <div>No items to display.</div> }

Key improvements:

  • track is mandatory, enforcing a performance best practice that was often forgotten.
  • The built-in @empty block cleans up templates by removing the need for a separate *ngIf.

You can use the CLI to automatically refactor your templates:

ng generate @angular/core:control-flow

2. Zoneless: Escaping the “Magic” of Change Detection

While still experimental, the path to a zoneless Angular is becoming clearer. In a zoneless world, the UI only updates when you explicitly tell it to.

Signals are the primary tool for this.

mySignal.set(newValue);

This line directly tells Angular to update only the specific parts of the DOM that depend on that signal. It’s a surgical, predictable, and high-performance approach that eliminates the overhead and unpredictability of Zone.js.

Conclusion

Angular 20 is a significant release. The upgrade requires manual intervention for testing, but it pushes the framework toward a more modern, explicit, and performant future. By embracing standalone components, a feature-based architecture, and the new control flow, you’re not just updating—you’re preparing your application for the next era of web development.

Happy coding!


Need a specific version jump? Every migration path — v19 through v22 — is collected in the Angular Upgrade Guide.

Written by

Antonio Cárdenas

Google Developer Expert in Angular · Verified GDE profile · Google Developers profile

My technical writing reaches 100,000+ developers in English and Spanish. I've migrated production Angular apps from v8 to v22 — everything I publish is built from real project work, not documentation re-reads.

Frequently asked questions

FAQs

What is the correct order to upgrade Angular across multiple major versions?

Always upgrade one major version at a time: v15 → v16 → v17 → v18 → v19 → v20 → v21. Never skip versions. Angular's schematics run sequentially and each migration depends on the previous one. Skipping versions omits critical breaking-change fixes and leaves your codebase in an inconsistent state.

What was the biggest change in the Angular v15 to v17 migration?

The v15→v17 migration centered on the transition to standalone components. Angular 15 introduced standalone APIs, Angular 17 made them the default for ng new. The migration involved running ng generate @angular/core:standalone to convert module-based components to standalone, removing NgModule declarations, and updating routing to use provideRouter().

When was Karma removed from Angular?

Karma was replaced as the default test runner in Angular 20 (May 2025), which introduced the new @angular/build package built on Vite/esbuild. Angular 21 completed the transition by making Vitest the default test runner for ng new projects. Run ng generate @angular/core:karma-to-vitest to migrate existing projects.

What is zoneless Angular and when should I migrate?

Zoneless Angular removes the dependency on Zone.js for change detection, replacing it with a Signals-based reactivity model that is faster and more predictable. It became opt-in in Angular 18, opt-out for new projects in Angular 21, and is the recommended architecture for greenfield projects from Angular 20 onwards. Migrate existing apps by running ng generate @angular/core:zoneless-migration.

What breaking changes should I expect when upgrading from Angular 19 to 20?

The main breaking changes from Angular 19 to 20 are: afterRender() renamed to afterEveryRender() (no auto-migration), TestBed.get() removed (use TestBed.inject()), InjectFlags enum removed, DOCUMENT token moved from @angular/common to @angular/core, and @angular-devkit/build-angular replaced by the new @angular/build package.

Keep reading

Building something with Angular?

I write about Angular architecture, Signals and the tooling around them — migration guides, real upgrade paths, and the catches most posts skip.

Browse all articlesAbout me

More from the blog