Getting started General
Components
Forms
Trends
Utilities
Plugins Sass Migrate from v1
  Join us
  Angular Articles

What's New in Angular v22: Features Breakdown

Angular

Angular v22 landed on June 3, 2026, and it's the release where years of signal-based groundwork finally become the default. OnPush change detection, Signal Forms, the resource API: they're all production-ready now. Here's the full breakdown of what actually changed, why it matters, and how to get your app onto it.

Angular v22: the signal-first release
Angular v22, June 2026 release

Angular v22 is a major release, but it won't ruin your week. Instead of dropping a pile of new APIs on you, the team took a long list of experimental features and marked them stable, then flipped a few defaults to match how people actually write Angular these days. If you've already moved to signals, standalone components, and the new control flow, a lot of v22 feels less like learning something new and more like the framework catching up to your code.

Let's go through it section by section, starting with the change that touches every component you'll ever write: a new default change detection strategy.

Angular v22 at a Glance

  • Release date: June 3, 2026
  • OnPush is now the default change detection strategy for every component
  • Signal Forms, the resource API, and Angular Aria are stable
  • Vitest replaces Karma and Jasmine as the default test runner
  • Incremental hydration is on by default for server-side rendered apps
  • HttpClient uses the Fetch API instead of XMLHttpRequest by default
  • New @Service decorator and injectAsync() for dependency injection
  • Requirements changed: TypeScript v6 and Node v22 are now mandatory

OnPush Is the New Default Change Detection Strategy

This is the one to pay attention to. For years, every component you created ran with the old "check everything, all the time" strategy, which re-runs a component's bindings on almost any event anywhere in the app. From v22 on, a component that doesn't declare a strategy gets ChangeDetectionStrategy.OnPush instead. Angular re-checks it only when its inputs change, a signal it reads updates, or an event fires inside it. Nothing more.

If your app already leans on signals and immutable inputs, you get fewer wasted checks and faster rendering for free, without touching a line. The catch is older code that quietly mutates objects in place: those views can stop updating. The good news is the migration handles it for you, dropping in the opt-out strategy wherever it's needed:

import { ChangeDetectionStrategy, Component } from '@angular/core';

// New behavior in v22: OnPush is implied if you write nothing.
@Component({
  selector: 'app-product',
  templateUrl: './product.html'
})
export class ProductComponent {}

// Opt back into the old "check always" behavior when you need it.
@Component({
  selector: 'app-legacy',
  changeDetection: ChangeDetectionStrategy.Eager,
  templateUrl: './legacy.html'
})
export class LegacyComponent {}

Notice the renamed value: the old default goes by ChangeDetectionStrategy.Eager now. When you run ng update, the schematic stamps it onto every component that didn't already set a strategy, so nothing changes behavior-wise on day one. From there you can peel it off one component at a time, checking each view still works under OnPush as you go.

Signal Forms Are Stable

Signal Forms finally shed the experimental label in v22. The whole API is built on signals, which means the form's value, its validation state, even whether a field has been touched, are all reactive signals you can read straight from a template or a computed. No FormGroup, no FormControl tree to babysit and keep in sync with your model.

You start with a plain object model wrapped in signal(), then describe how it should validate with the form() function and a schema:

import { Component, signal } from '@angular/core';
import { form, required, email, minLength } from '@angular/forms/signals';

@Component({
  selector: 'app-login',
  templateUrl: './login.html'
})
export class LoginComponent {
  model = signal({ email: '', password: '' });

  loginForm = form(this.model, (path) => {
    required(path.email);
    email(path.email);
    required(path.password);
    minLength(path.password, 8);
  });

  submit() {
    if (this.loginForm().valid()) {
      console.log(this.model());
    }
  }
}

In the template you bind fields with the Field directive and read errors straight from the form signal:

<form (submit)="submit()">
  <input type="email" [field]="loginForm.email" />
  <input type="password" [field]="loginForm.password" />

  <p>{{ loginForm.password().getError('minLength') }}</p>

  <button [disabled]="loginForm().invalid()">Sign in</button>
</form>

The stable release sprinkles in the things you actually reach for day to day: minDate() and maxDate() validators, a getError() method that narrows types properly, debouncing on blur and on async validators, and a touch() output that retires the awkward mutable touched flag. And if you've got custom controls written the old way with ControlValueAccessor, they keep working with Signal Forms, so you don't have to rewrite everything at once to adopt this.

The resource API Is Production-Ready

resource(), rxResource(), and httpResource() are stable as of v22. Their whole point is to keep async data living inside the signal graph, so you stop hand-stitching RxJS and signals together every time you fetch something. An httpResource refetches on its own whenever a signal it depends on changes, and it hands you loading, error, and value as signals you can read directly.

import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';

@Component({
  selector: 'app-user',
  templateUrl: './user.html'
})
export class UserComponent {
  userId = signal(1);

  // Refetches automatically whenever userId() changes.
  user = httpResource(() => `/api/users/${this.userId()}`);
}
@if (user.isLoading()) {
  <p>Loading...</p>
} @else if (user.error()) {
  <p>Something went wrong.</p>
} @else {
  <h2>{{ user.value()?.name }}</h2>
}

Two things in the stable release are worth calling out. There's a new chain() method that makes feeding one resource's result into the next painless, with loading and error states propagating on their own. And if you pass an id in the options, a resource resolved during server-side rendering gets cached and shows up instantly on the client, no second loading flash while it refetches.

The @Service Decorator and injectAsync()

Spinning up a singleton service has always meant typing out @Injectable({ providedIn: 'root' }). The new @Service() decorator says the exact same thing with a lot less ceremony. Out of the box it gives you a tree-shakeable, app-wide singleton, same as the root-provided Injectable, except now the name tells you what it is:

import { Service, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Service()
export class ProductService {
  private http = inject(HttpClient);

  getProducts() {
    return this.http.get('/api/products');
  }
}

One thing to know: a @Service pulls its dependencies through inject() rather than the constructor, which lines it up with how the rest of modern Angular already works.

Sitting right next to it is injectAsync(), which lazy-loads a service through a dynamic import so its code ships in its own chunk and only downloads the first time someone needs it. If you'd rather not pay that cost on demand, you can have it prefetch quietly while the browser is idle:

import { injectAsync, onIdle } from '@angular/core';

export class CheckoutComponent {
  private heavyService = injectAsync(
    () => import('./pricing.service').then((m) => m.PricingService),
    { prefetch: onIdle }
  );
}

Vitest Replaces Karma and Jasmine

New projects in v22 ship with Vitest as the test runner. Karma has been on its way out for a while, and Jasmine is no longer what you get by default. Vitest is quick, runs in watch mode without you asking, and reuses the same build config your app already has, so you're not maintaining a separate toolchain just for tests.

Existing projects keep running as-is, and the CLI gives you schematics to make the jump. To convert a Karma and Jasmine setup, run:

ng generate @schematics/angular:migrate-karma-to-vitest

Worried about your async tests? If you lean on fakeAsync, flush, or waitForAsync, those Zone.js helpers run under Vitest now through a dedicated patch, so there's nothing to rewrite. A couple of new flags help too: --isolate runs tests in separate processes, and --quiet keeps build noise out of your output.

Incremental Hydration by Default

If you render on the server, incremental hydration is on by default now. Rather than waking up the whole page in one shot, Angular hydrates pieces of it as they scroll into view or as the user interacts with them. That means less JavaScript running on first load and better interaction numbers on heavy pages. The old withIncrementalHydration() call is deprecated, since you get the behavior for free. While we're here: provideServerRendering() picked up a maxResponseBodySize option that defaults to 1 MB.

HttpClient Switches to the Fetch API

Under the hood, HttpClient now talks to the browser Fetch API instead of XMLHttpRequest. The withFetch() you used to opt into is simply the default, and if something in your stack still needs the old transport you can switch back with withXhr(). Fetch keeps Angular aligned with the modern platform, behaves better outside the browser, and gets along nicely with server-side rendering.

import { provideHttpClient, withXhr } from '@angular/common/http';

export const appConfig = {
  providers: [
    // Fetch is the default now; opt out only if you must.
    provideHttpClient(withXhr())
  ]
};

Template and Compiler Improvements

Templates keep getting more capable and stricter at the same time. The change you'll notice first is being able to drop an HTML comment inside an opening tag, which is genuinely handy when you want to annotate or temporarily kill a single attribute on a long element:

<input
  type="text"
  [value]="name()"
  <!-- [disabled]="locked()" temporarily off -->
  (input)="onInput($event)"
/>

Beyond that, the compiler got smarter about optional chaining with automatic null guards, added exhaustive @switch checks, gave @defer (on idle(500ms)) a timeout so it can't wait forever, and now catches duplicate input or output names and broken @for loops at build time instead of letting them slip through to runtime. Strict templates are on by default too, and the migration adds strictTemplates: false for any project that isn't ready for that yet.

AI Integration with WebMCP

v22 also takes an experimental swing at AI with WebMCP, a browser protocol that lets AI agents call into your running app. The part that caught my eye is automatic form exposure: wire up the providers, tag a Signal Form, and Angular reads the form's model, builds a JSON schema from it, and makes the whole thing callable by an agent. The agent even gets the validation errors back and can fix its own input.

import { provideExperimentalWebMcpForms } from '@angular/forms/signals';

export const appConfig = {
  providers: [
    provideExperimentalWebMcpForms()
  ]
};

The team also shipped official Angular skills you can bolt onto AI coding tools with npx skills add https://github.com/angular/skills, and your signal and dependency injection graphs now show up as Chrome DevTools extensions. All of this is experimental, so don't be surprised when the API shifts around before it settles.

Angular Aria Is Stable

The @angular/aria package made it from developer preview to production-ready. It hands you accessible building blocks, things like keyboard navigation and the ARIA wiring nobody enjoys writing by hand, that you compose into your own components, and it plugs straight into Signal Forms. If you maintain a design system in-house, this is the accessibility groundwork without dragging in a whole component library to get it.

Security Hardening

There's a real security story here too. v22 added server-side request forgery (SSRF) protections around how platform-server sets up location and document, tightened sanitization for dynamic href attributes on SVG anchors, and hardened locale data against prototype pollution. The transfer cache now skips any request carrying cookies or using withCredentials by default, so sensitive responses don't get baked into the page. None of this asks anything of you, but it's nice to know your app comes out the other side of the upgrade a little safer.

Breaking Changes to Watch

The migrations cover most of v22, but a handful of changes are worth eyeballing yourself before you ship:

  • TypeScript v6 and Node v22 are required. TypeScript 5.9 and Node 20 are no longer supported.
  • OnPush is the default. Views that relied on mutating objects in place may stop updating until you switch them to Eager or move to signals.
  • Router parameter inheritance changed. paramsInheritanceStrategy now defaults to 'always', and this one is not auto-migrated.
  • Optional chaining returns undefined instead of null in templates; a migration can preserve the old behavior if you depend on it.

How to Upgrade to Angular v22

Before anything else, get yourself onto Node v22 and make sure the project is already on Angular v21, since the CLI only jumps one major version at a time. Then run the update:

# Check your current version
ng version

# Update the core framework and CLI to v22
ng update @angular/core@22 @angular/cli@22

The schematics bump TypeScript, drop ChangeDetectionStrategy.Eager in where it's needed, and clean up deprecated APIs along the way. Once it's done, run your tests, keep an eye out for views that stopped updating under OnPush, and double-check any code that pulled router parameters from a parent route still does what you expect. When everything's green, start pulling those Eager markers back out so you actually get the benefit of the new default.

Conclusion

v22 isn't really about shiny new toys. It's about making the modern, signal-first way of building Angular the path of least resistance. OnPush by default pays you back for writing reactive code. Stable Signal Forms and the resource API take away the last few reasons to reach for the older patterns. Vitest, incremental hydration, and the Fetch-based HttpClient quietly modernize the tooling underneath you. And @Service trims a little boilerplate off every service you write.

The upgrade is mostly automated, the breaking changes are manageable, and what you get out of it is a faster app with cleaner code. If you're already writing signals and standalone components, v22 is going to feel like home. When you're ready to dig into the specifics, the official Angular v22 release page and the Angular update guide are the two links to keep open.

Start Building with Axentix

Ready to create amazing websites? Get started with Axentix framework today.

Get Started

Related Posts