Le blog
de
Ninja Squad

What's new in Angular 22.1?

Angular 22.1.0 is here!

Angular logo

This is a minor release with some nice features, but the main news is the shift to a yearly major release cadence, with a new major version every year in June! This means v23 will land in June 2027 (instead of November 2026), v24 in June 2028, and so on. This was a popular demand to limit the number of major releases containing breaking changes, even if it doesn't mean that there will be fewer of them: they'll just be grouped in a single major release per year instead of two. Major releases are now supported for 2 years instead of 18 months.

Minor releases will be released 4-6 times a year, every two months, and will contain new features and bug fixes as usual.

linkedSignal custom setter

linkedSignal is a handy function when you want to create a signal that is linked to another signal, but that you need to write to (and thus can't use a simple computed signal).

readonly items = signal<Array<ItemModel>>([]);
// 👇selectedItem is reset to the first item of the items signal when it changes
// but is also writable
protected readonly selectedItem = linkedSignal(() => this.items()[0]);

protected selectItem(item: ItemModel) {
  this.selectedItem.set(item);
}

In v22.1, you can now provide a custom setter to linkedSignal, which allows to write back to the source signal in a custom way.

readonly items = signal<Array<ItemModel>>([]);
protected readonly selectedItem = linkedSignal(() => this.items()[0], {
  // 👇writes back to the items signal in a custom way
  set: (item: ItemModel) => {
    // if the item is not in the items signal, then add it
    const items = this.items();
    const index = items.indexOf(item);
    if (index < 0) {
      this.items.set([item, ...items]);
    }
  }
});

I don't think this is going to be used very often, but it can be useful in some cases.

JSONP support deprecated

Angular v22.1 deprecates JSONP support in the HTTP client.

JSONP is an old technique that works by adding a <script> tag to the page and executing the response as JavaScript in the global context. This makes it prone to XSS vulnerabilities, and it also bypasses modern Content Security Policies.

As a result, the JSONP-related APIs like withJsonpSupport() are now deprecated.

If your application still uses these APIs, you should plan to migrate away from JSONP and use standard HTTP requests instead. Angular now also prints a warning in development mode when the JSONP backend is instantiated, as JSONP support is intended to be removed in a future version.

Effects and HTTP interceptors

As you know, effects automatically subscribe to the signals they read. Usually the dependencies are fairly obvious, but in some cases, they are not. For example, an effect that triggers an HTTP request was depending on signals read in the HTTP interceptors invoked! I say was, as this has been fixed in v22.1: interceptors are now untracked automatically. This should avoid some unexpected behaviors in effects that trigger HTTP requests, but to be safe, we usually recommend to use untracked() in effects anyway:

effect(() => {
  const value = this.mySignal();
  untracked(() => {
    // do something with `value`
  });
});

@Injectable to @Service migration

Angular v22 introduced the new @Service() decorator, and the CLI now generates services using it by default. At the time, there was no automatic migration available, but v22.1 adds one.

You can now run:

ng generate @angular/core:service

This schematic converts eligible services from:

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

@Injectable({ providedIn: 'root' })
export class UserService {
  // ...
}

to:

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

@Service()
export class UserService {
  // ...
}

Classes using a bare @Injectable() are migrated to @Service({ autoProvided: false }).

The migration is conservative and skips services that may need manual attention: classes using constructor-based dependency injection, classes passing options other than providedIn to @Injectable, and classes using a value other than 'root' for providedIn.

Devtools

Two notable improvements in the devtools:

  • it is now possible to search the signal graph by name and by type via type:computed for example;
  • the transfer state panel has been improved and is now shown by default (it was opt-in).

Also note that Angie, the official Angular mascot, is starting to show up in the docs and the devtools.

Angular CLI

As explained in our Angular v22 article, the chunk optimization introduced in Angular v18.1 is now enabled by default in production builds. In v22.0, this optimization switched back to Rollup by default, while Rolldown was still available experimentally via the NG_BUILD_CHUNKS_ROLLDOWN environment variable. Chunk optimization is now also enabled for server builds. It was not the case previously as it was breaking preloading, but this is now resolved.

Rolldown is now stable, and Angular v22.1 uses it by default for chunk optimization. You can opt back into Rollup with:

NG_BUILD_CHUNKS_ROLLDOWN=false ng build

AI

Angular v22.1 promotes a few MCP tools from experimental to stable. They are now registered by default when you start the Angular CLI MCP server, without needing to enable them with the hidden --experimental-tool flag.

The run_target tool is now stable, and lets an AI agent run an Angular target from your workspace, for example a build or a test target.

The dev-server tools are also stable: devserver.start, devserver.stop, and devserver.wait_for_build.

The CLI now also shares the build cache across git worktrees, which are commonly used in AI-assisted development workflows.

Worth noting for those of you who bought our ebook: we added a new chapter about AI in Angular, which is available to all our readers.

Summary

That's all for this release, and the next one will be v22.2, which is expected in September 2026. Stay tuned!

All our materials (ebook, online training and training) are up-to-date with these changes if you want to learn more!

What's new in Angular 22.0?

Angular 22.0.0 is here!

Angular logo

This release continues the modernization work started in the previous versions: Signal Forms and resources are now stable, OnPush is now the default change detection strategy for components, and the HTTP client now uses Fetch by default.

There are also a new decorator, and quite a few additions around AI tooling.

Let's dive in!

TypeScript v6 and Node v22 required

Angular v22 now requires TypeScript v6. Older versions of TypeScript, including v5.9, are not supported anymore.

Angular v22 also drops support for Node v20, and Node v26 is supported.

OnPush by default

Angular offers two change detection strategies:

  • Eager (which was previously named Default),
  • OnPush.

Until Angular v21, the default strategy was Eager. Since Angular v22, the default strategy is now OnPush!

This means that a component that does not explicitly specify a change detection strategy will now use OnPush instead of Eager. Of course, there is a migration available to automatically add changeDetection: ChangeDetectionStrategy.Eager to all components that do not specify a strategy yet, so that they keep using the Eager strategy when you upgrade to Angular v22. It also replaces Default by Eager in the component decorators if needed.

If a component is already using OnPush, then the migration keeps the changeDetection property from the component decorator, even if it is not needed anymore.

Signal Forms are stable!

One of the biggest changes in Angular v22 is that the signal forms APIs are no longer experimental! They graduated to stable and are now available for production use.

👉 To learn more about signal forms, check out our dedicated articles.

A few changes landed before the stabilization though, so let's start with the breaking ones before going into the new features.

touch/touched

Custom form components used to be able to bind to the touched model, but after reflecting on the API, the Angular team realized that this was not the best approach, as it was allowing a control to mark the field as no longer touched. Instead, we now have a touched input to know if the field is touched or not, and a touch() output to mark the field as touched. You'll have to manually make this change in your codebase if you were using the touched model before.

markAsTouched()

markAsTouched() now marks a field and all its descendants as touched, instead of only the field itself. This behavior can be overridden by passing { skipDescendants: true } as an argument to the method.

Consistent when

All validators and dynamic behavior functions (disabled, readonly, hidden, etc.), now have a when option to specify when they should be applied, instead of passing the reactive function directly as an argument. The old signature is still supported for backward compatibility, but is deprecated:

// before
disabled(form.age, ({ valueOf }) => valueOf(form.isAdmin));
// 👇after
disabled(form.age, { when: ({ valueOf }) => valueOf(form.isAdmin) });

minDate() and maxDate() validators

Two new validators minDate() and maxDate() were added, allowing you to easily validate date inputs:

protected readonly userForm = form(
  model,
  form => {
    // ...
    // 👇date should be between January 1st, 1900 and today
    minDate(form.birthDate, new Date('1900-01-01'));
    maxDate(form.birthDate, new Date())
  }
);

This adds minDate and maxDate errors to the field if the date is out of range.

debounce a field on blur

Signal forms already supported debouncing the value changes of a field, by using the debounce function and specifying a delay in milliseconds or a function that returns a promise for fine-grained control. It is now also possible to debounce the value changes on blur, by using the 'blur' option:

form => {
  // ...
  // 👇debounce the password field on blur
  debounce(form.password, 'blur')
}

debounce async validators

As you sometimes want to debounce only the asynchronous validators of a field, we now have a debounce option in validateAsync() and validateHttp():

validateHttp(form.