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-vitestto 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
HttpClientby expecting it not to be there, they might fail. - If you rely on
HttpClientModulefor 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);
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>
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>
}
🛠️ The Upgrade Checklist
Ready to jump? Follow this order to minimize pain.
- Backup: Commit everything. Seriously.
-
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 -
Update Local Project:
Now update your local project dependencies:ng update @angular/cli@21 @angular/core@21 -
Run the Diagnostics:
Angular 21 includes smarter diagnostics. Pay attention to warnings aboutngClass(soft deprecated in favor of[class.my-class]) and standalone migration opportunities. -
Check Your Tests:
Runng test. If it explodes, decide:- Path A: Keep Karma (add
@angular/build:karmamanually if removed). - Path B: Migrate to Vitest (Recommended).
- Path A: Keep Karma (add
-
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.
