DEV Community

Cover image for Angular 20 to 21 Upgrade: The Practical Survival Guide
Antonio Cardenas for Turing's Oracle

Posted on • Originally published at yeou.dev

Angular 20 to 21 Upgrade: The Practical Survival Guide

A clear and concise guide to upgrading from Angular 20 to 21. that Covers the essentials like the Karma full removal, the new default Zoneless mode, automatic HttpClient, and how to fix your breaking builds.

If you thought Angular 20 was a big shift, welcome to Angular 21.

While version 20 was about stabilizing Signals, version 21 is about removing the old guard. The "Angular Way" has fundamentally changed: zone.js is optional, Karma is dead, and RxJS is slowly retreating to the edges.

This isn't just an update; it's a new ecosystem. Here is what is going to break and how to fix it.

🚨 The "Stop Everything" Breaking Changes

Before you run ng update, be aware that your build will likely fail if you rely on these legacy patterns.

1. The Karma Extinction Event (Vitest is Default)

The most immediate shock for many teams will be ng test. Angular 21 has officially swapped Karma for Vitest as the default test runner.

What breaks: If you have a custom karma.conf.js or rely on specific Karma plugins/reporters, your test suite is now legacy code.

The Fix:

  • New Projects: You get Vitest out of the box. It's faster, cleaner, and uses Vite.
  • Existing Projects: You aren't forced to switch immediately, but the writing is on the wall. The CLI will nag you.
  • Migration: Run the schematic ng generate @angular/core:karma-to-vitest to attempt an auto-migration. It's remarkably good at converting standard configs, but custom Webpack hacks in your test setup will need manual rewriting for Vite.

2. HttpClient is "Just There"

Remember adding provideHttpClient() to your app.config.ts or importing HttpClientModule?

The Change: HttpClient is now injected by default in the root injector.

What breaks:

  • If you have tests that mock HttpClient by expecting it not to be there, they might fail.
  • If you rely on HttpClientModule for complex interceptor ordering in a mixed NgModule/Standalone app, you might see subtle behavior changes.

The Fix: Remove explicit provideHttpClient() calls unless you are passing configuration options (like withInterceptors or withFetch). It cleans up your config, but check your interceptor execution order.

3. zone.js is Gone (For New Apps)

New apps generated with ng new will exclude zone.js by default.

What breaks: Nothing for existing apps (yet). Your polyfils.ts will keep importing Zone.

The Warning: If you copy-paste code from a new v21 tutorial into your existing v20 app, it might assume Zoneless behavior (using ChangeDetectorRef less often, relying on Signals). If you mix the two paradigms without understanding them, you'll get "changed after checked" errors or views that don't update.

✨ The New Toys: Features You'll Actually Use

Once you fix the build, v21 offers some incredible DX improvements.

1. Signal Forms (Experimental but Stable)

This is the feature we've been waiting for. No more valueChanges.pipe(...) spaghetti.

import { form, field } from '@angular/forms/signals';

// Define a reactive form model
const loginForm = form({
  email: field('', [Validators.required, Validators.email]),
  password: field('', [Validators.required])
});

// Access values directly as signals!
console.log(loginForm.value().email); 
Enter fullscreen mode Exit fullscreen mode

Why use it: It's type-safe by default and doesn't require RxJS mastery.

Status: Experimental. Use it for new features, but maybe don't rewrite your checkout flow just yet.

2. Angular Aria (Developer Preview)

A new library of headless primitives for accessibility.

Instead of fighting with aria-expanded and role="button", you use directives that handle the a11y logic while you handle the CSS.

<!-- Handles keyboard nav, focus, and ARIA roles automatically -->
<div ariaMenu>
  <button ariaMenuItem>Option 1</button>
  <button ariaMenuItem>Option 2</button>
</div>
Enter fullscreen mode Exit fullscreen mode

3. Regex in Templates

Small but mighty. You can finally use regex literals in templates, perfect for @if logic without creating a helper function.

@if (email() | match: /@company\.com$/) {
  <span class="badge">Employee</span>
}
Enter fullscreen mode Exit fullscreen mode

🛠️ The Upgrade Checklist

Ready to jump? Follow this order to minimize pain.

  1. Backup: Commit everything. Seriously.
  2. Update the Global CLI:
    Updating Angular generally involves two parts: the global CLI and the local project dependencies. Ensure your global CLI is up to date first (you might need sudo or Administrator privileges).

    # Optional: Uninstall the old global version first to avoid conflicts
    npm uninstall -g @angular/cli
    
    # Verify the npm cache
    npm cache verify
    
    # Install the latest global CLI version
    npm install -g @angular/cli@latest
    
  3. Update Local Project:
    Now update your local project dependencies:

    ng update @angular/cli@21 @angular/core@21
    
  4. Run the Diagnostics:
    Angular 21 includes smarter diagnostics. Pay attention to warnings about ngClass (soft deprecated in favor of [class.my-class]) and standalone migration opportunities.

  5. Check Your Tests:
    Run ng test. If it explodes, decide:

    • Path A: Keep Karma (add @angular/build:karma manually if removed).
    • Path B: Migrate to Vitest (Recommended).
  6. Optional: Go Zoneless:
    If you're feeling brave, run the experimental migration:

    ng generate @angular/core:zoneless-migration
    

This is "Agentic" territory. See our MCP Guide for how to let AI handle this complex refactor.

Summary

Angular 21 is the "clean slate" release. It sheds the weight of the last decade (Zone, Karma, Modules) to compete with modern frameworks like Svelte and Solid.

The upgrade might be bumpy due to the testing changes, but the destination—a faster, simpler, signal-driven framework—is absolutely worth it.

Top comments (0)