What’s New in Angular 21: Zoneless by Default, Signal, Vitest, and Modern Template Syntax

Angular 21 Upgrade

Angular 21 landed in late 2025 and continues Angular’s recent “modernization streak”: better reactivity with signals, cleaner template syntax, a sharper performance story, and tooling that feels more current.

This guide is written for real projects—not hello-world demos. You’ll get:

  • What’s new in Angular 21 (and why it matters)
  • A safe upgrade path (including jumping from older versions)
  • Common issues + fixes (zoneless, tests, signals pitfalls)
  • Modern template syntax: @if, @for, @switch, @let, @defer
  • How to measure performance improvements vs. Angular <18

Along the way, I’ll also link to ngbootstrap—my open-source Angular UI library built for Angular 21 with Bootstrap-friendly styling (Datagrid, Drag & Drop, Stepper, Splitter, Tree, Typeahead, and more). GitHub+2ngbootstrap+2

1) What’s new in Angular 21 (high impact)

Zoneless is the default direction

Angular 21 pushes zoneless change detection as the modern baseline for new apps—less “magic”, more explicit reactivity, and typically less overhead from Zone.js. Angular Blog+2angular.dev+2

Key practical implications:

  • You’ll often remove zone.js and zone.js/testing from angular.json polyfills to get full benefit (and avoid warnings). angular.dev+1
  • If you still want NgZone behavior (or need eventCoalescing), you can explicitly opt in. angular.dev

Why it matters: zone-patched async can trigger unnecessary change detection. Zoneless shifts you toward signals + explicit triggers.

Vitest becomes a first-class default testing story

Angular 21 promotes Vitest as the stable, modern unit test runner experience, and provides a schematic to help refactor Jasmine tests.

Migration command:

ng g @schematics/angular:refactor-jasmine-vitest

Signal Forms (experimental)

Angular 21 expands the signals ecosystem with Signal Forms—an experimental, model-driven way to manage forms.

It’s explicitly marked experimental, so treat it as: “try it, learn it, pilot it—don’t bet the whole enterprise app on it yet.”

Developer experience upgrades (templates + tooling)

Modern Angular templates are now much more “semantic”:

  • @if / @else, @for, @switch control flow.
  • @let for local template variables.
  • @defer for deferrable views with improved viewport trigger options (IntersectionObserver options support).

2) Before you upgrade: compatibility and the safe path

Check Node/TypeScript compatibility first

Angular versions are strict about runtime/tooling versions. Confirm your Node.js and TypeScript match Angular 21 requirements before touching code. angular.dev

If you’re jumping multiple majors, upgrade one major at a time

Angular recommends updating across majors step-by-step (ex: 18 → 19 → 20 → 21). angular.dev
If you skip versions, you risk missing migrations and breaking changes.

3) Upgrade steps (works for most real apps)

Step 0 — Prepare the repo

  • Create a branch (upgrade/angular-21)
  • Ensure tests are green on current version
  • Snapshot bundle metrics (see performance section)

Step 1 — Upgrade Angular + CLI (major-by-major)

For each major step (example: 20 → 21):

ng update @angular/core@21 @angular/cli@21

Repeat for each major version if you’re coming from older Angular.

Step 2 — Run the Angular update guide checklist

Use Angular’s update guide to catch version-specific migrations and manual steps. angular.dev

Step 3 — Decide: zoneless or zone-based

Option A (recommended for Angular 21-style apps): Zoneless

import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [
    provideZonelessChangeDetection(),
  ],
});

Then remove Zone.js from polyfills to avoid NG0914 warnings and reduce overhead:

  • remove zone.js and zone.js/testing from angular.json polyfills (build + test) angular.dev+1

Option B: Keep NgZone (if you rely on Zone.js behavior)

import { provideZoneChangeDetection } from '@angular/core';

bootstrapApplication(AppComponent, {
  providers: [
    provideZoneChangeDetection({
      eventCoalescing: true,
    }),
  ],
});

provideZoneChangeDetection is also where eventCoalescing lives.

In plain terms, eventCoalescing: true groups rapid-fire events so Angular runs fewer change detection passes.

Step 4 — Tests: migrate to Vitest (optional but worth it)

ng g @schematics/angular:refactor-jasmine-vitest

4) Common Angular 21 upgrade issues (and fixes)

Issue A — “Zoneless but still loading Zone.js” (NG0914)

You enabled zoneless change detection, but Zone.js is still included (usually via polyfills). Fix by removing it from angular.json (and polyfills.ts if you import it there).

Issue B — effect() error: “can only be used within an injection context”

This happens when effect() is called outside Angular’s injection context. Fix by moving it into:

  • a constructor
  • a field initializer
  • a function invoked within an injection context

A safe, common pattern is defining effects in the constructor of a component/service that’s already DI-created.

Issue C — Query params “changed but didn’t trigger”

When you switch to signals, it’s easy to accidentally stop listening to router state properly. The robust pattern is:

  • keep router streams as Observables
  • convert them to signals with toSignal() (or keep them as streams and derive state)

Example approach (conceptual):

  • read ActivatedRoute.queryParamMap
  • map to the value you need
  • distinctUntilChanged()
  • then trigger your API call

Issue D — Third-party libraries expecting Zone.js

If you rely on libraries that assume zone-patched async behavior, you may need to:

  • keep Zone.js (use provideZoneChangeDetection)
  • or upgrade/replace the dependency

(Zoneless is great, but the real world has dependencies.)

5) Modern Angular template syntax (Angular 21 “semantic” style)

@if / @else instead of *ngIf

@if (user()) {
  <app-profile [user]="user()" />
} @else {
  <app-skeleton />
}

@for instead of *ngFor (with clear tracking)

@for (row of rows(); track row.id) {
  <li>{{ row.name }}</li>
}

@switch for readable conditional rendering

@switch (status()) {
  @case ('loading') { <app-spinner /> }
  @case ('error') { <app-error /> }
  @default { <app-content /> }
}

@let to reduce repeated pipes and expressions

@let q = (query() ?? '').trim();

@if (q.length > 0) {
  <p>Searching for: {{ q }}</p>
}

@defer for real-world lazy UI

@defer can load expensive UI only when it’s needed (example: when scrolled into view). angular.dev

Angular 21 also supports customizing IntersectionObserver options for viewport triggers (useful for preloading before content becomes visible). Angular Blog+1

6) Signals: the practical patterns that avoid bugs

Keep signals “pure” and derived

  • signal() for state
  • computed() for derived values
  • effect() for side-effects (API calls, logging, syncing)

Golden rule: don’t mutate state inside a computed.

A clean signal-driven search pattern (pseudo-real)

  • q is a signal
  • normalize it
  • debounce/throttle (optional)
  • call API in an effect and write into another signal

If you’re migrating from RxJS-heavy code, it’s fine to keep RxJS for streams and use signals for UI state.

7) Performance: Angular 21 vs Angular <18 (what changes + how to measure)

What Angular improved since “pre-18”

Angular’s performance story got a real boost starting around v17:

  • Built-in control flow (like @for) reported up to 90% faster runtime in public benchmarks for loops, and major build speed gains (SSR/hybrid builds). Angular Blog
    Those wins carry forward into modern Angular (including 21), especially when you adopt the newer syntax and deferrable views.

From v18 onward, zoneless became the big lever:

  • Removing Zone.js can reduce overhead and even reduce bundle size when you remove it from polyfills.

“For Angular 21 projects that need Bootstrap-friendly, production-focused components (Datagrid, Drag & Drop, Pagination, Stepper, Splitter), I’ve been building ngbootstrap as a modular, standalone-first library.”

8) Bonus: Add a practical UI upgrade while you’re here (ngbootstrap links)

If your Angular 21 migration includes modernizing UI components (tables, pagination, stepper, builder-style UIs), here are relevant links you can drop inline in your post: