Le blog
de
Ninja Squad

What's new in Angular 20.2?

Angular 20.2.0 is here!

Angular logo

This release marks a pivotal moment for the framework: the stabilization of Zoneless change detection! v20.2 also introduces a complete overhaul of the animations API, and new AI features.

Let's dive in.

Zoneless is stable!

Angular v20.2 marks the stability of the Zoneless API (provideZonelessChangeDetection()), which allows developers to build Angular applications without the need for zone.js. New applications are not zoneless by default yet, but this should be the case in the next major version.

New animations support

This version marks a significant turning point for animations in the framework. @angular/animations is now deprecated and replaced with a new, simpler, API. The animations package hasn't seen major updates since its original author left the Angular team a few years ago, and it's not been actively maintained since then. The old API was a bit complex, heavy, and built in a time where browsers did not have advanced CSS features. Most animations can now be written using pure CSS.

A RFC has been opened last June to discuss the use-cases where pure CSS is not enough. As a result, Angular 20.2 introduces a new (stable) API for animations to handle these cases.

The problem is that what we often want is to apply a transition effect when an element appears in the DOM or disappears from the DOM. But @if and @for don’t change the style of an element: they simply add or remove elements from the DOM. So we need something more to, for example, have fade-in and fade-out transitions when elements appear and disappear.

Angular lets you animate elements with the animate.enter/animate.leave syntax, introduced in v20.2, allowing you to add classes to an element when it enters or leaves the DOM. These classes are then removed by Angular when the associated animation is done.

@if (display()) {
  <div animate.enter="fade-in" animate.leave="fade-out">Content</div>
}

So here the animated classes are going to be fade-in and fade-out. Let’s define a "fading" effect in CSS using these classes:

.fade-in {
  /* initial state */
  @starting-style {
    opacity: 0;
  }
  transition: opacity 300ms;
  opacity: 1;
}

.fade-out {
  transition: opacity 300ms;
  opacity: 0;
}

Here the transition will last one second, and will progressively change the opacity from 0 to 1 when the element enters, and from 1 to 0 when it leaves.

Every time the condition of the @if changes, the element fades in or fades out over a second, instead of appearing or disappearing brutally.

Your imagination (and CSS skills) is the limit here: you can apply whatever effect you want!

You can use bindings with animate.enter and animate.leave, to bind a signal value, a method result or an array of classes for example:

@if (display()) {
  <div [animate.enter]="enterClasses()">Content</div>
}

If you don’t want to rely on CSS animations and prefer to use JavaScript animations, with the power of libraries like GSAP, then you can use animate.enter and animate.leave with the event binding syntax to call a method when the element enters or leaves:

@if (display()) {
  <div (animate.leave)="rotate($event)">Content</div>
}

The method is called with an AnimationCallbackEvent parameter, containing the target that is entering or leaving and a completeAnimation method that you must call when the animation is done:

protected rotate(event: AnimationCallbackEvent) {
  gsap.to(event.target, {
    rotation: 360,
    duration: 0.3,
    onComplete() {
      event.animationComplete();
    }
  });
}

In tests, the animations are turned off by default, so you don’t have to worry about them. If you want to test animations, you can configure the TestBed to enable animations with the animationsEnabled option:

TestBed.configureTestingModule({
  animationsEnabled: true
});

With this option enabled, elements won’t be removed until the animations are done.

You can check out the new chapter in our ebook on animations to learn more.

Templates

ARIA bindings

If you care about accessibility, you know how important ARIA (Accessible Rich Internet Applications) attributes are. Angular v20.2 introduces a new feature to make working with ARIA attributes easier and more intuitive. Until now, we had to use the attr. prefix to bind ARIA attributes, like this:

<div role="progressbar" [attr.aria-valuenow]="value()"></div>

With the new feature, you can now bind ARIA attributes directly without the attr. prefix:

<div role="progressbar" [aria-valuenow]="value()"></div>

or even:

<div role="progressbar" [ariaValueNow]="value()"></div>

Angular will automatically set the proper ARIA attributes instead of looking for aria properties.

Alias in @else if blocks

The control flow syntax now allows defining an alias in @else if blocks. Until now, it was only possible to use as in the first @if block, but now you can also use it in @else if blocks as well:

@if (admin(); as a) {
  Welcome Admin {{ a.name }}!
} @else if (user(); as u) {
  Welcome {{ u.name }}!
}

New characters allowed in templates

When the control flow syntax was introduced in Angular v17, it was not possible to use the @ character in templates. For example to display a user's email address, you had to use the user&#64;mail.com property instead of user@mail.com. This is no longer the case in Angular 20.2, and you can now use the @ character directly in templates. The { and } characters are still not allowed, so you still have to use the &#123; and &#125; in that case.

The compiler also accepts more characters in the property binding syntax, so you can have / or other [ ] characters in your property names. This is mainly to accommodate Tailwind CSS users, who can have classes with these characters. The compiler will now correctly parse bindings like [class.text-primary/80] and [class.data-[size='large']].

Extended diagnostics

A new extended diagnostic uninvokedFunctionInTextInterpolation has been added to warn you when you forget to call a method in an interpolation.

For example, if you have a getUserName() method that returns the user's name, and used it in a template like this:

<p>{{ getUserName }}</p>

throws:

[ERROR] NG8117: Function in text interpolation should be invoked: getUserName().

Forms

It is now possible to push an array of form controls into a FormArray using the push method. Until now you had to push them one by one.

Router

The Router.getCurrentNavigation method has been deprecated and replaced with currentNavigation signal. The signal returns the same thing as the previous method, a Navigation object or null. A migration has been provided to automatically update your code when running ng update.

Performances

The internal algorithm for signals has been changed to use linked lists instead of arrays, as Preact, Vue and other libraries have done. This should improve performance, and maybe put the Angular implementation slightly higher in the reactivity benchmarks (where it currently ranks dead last).

Testing

The TestBed.createComponent and TestBed.configureTestingModule methods have a new option inferTagName. When inferTagName is set to true, the TestBed will automatically infer the tag name of the component being tested based on its selector and use this tag name in the generated DOM. Until now (or when the option is false, as it is by default), the TestBed would use a div to represent the component, which is different from what would happen at runtime. This is usually not a problem, but the plan is to switch this option to true by default in a future version to limit the discrepancies between the test and runtime environments.

Service worker

The Service Worker package has received several improvements in v20.2, focusing on better error handling and developer experience.

Angular now provides better error handling with the addition of messageerror event handling and logging. This enhancement allows developers to capture and debug communication errors between the service worker and the main application thread more effectively.

The service worker has also gained improved storage management with better detection of storage full scenarios when caching data. This prevents silent failures and provides clearer feedback when 95% of the browser's storage quota is reached during data caching operations.

A new updateViaCache option is now supported in provideServiceWorker(), giving developers more control over how the service worker updates itself. This option allows you to specify whether the service worker script should bypass the browser cache when checking for updates, which can be set to 'all', 'none', or 'imports'.

Another option, type, allows you to specify the type of the ServiceWorker script to register('classic' or 'module', defaults to 'classic'). When specifying 'module', it registers the script as an ES module and allows use of import/export syntax and module features.

Finally, the service worker now notifies clients about version failures with more detailed error information. When a service worker update fails, clients now receive VERSION_FAILED events that include specific error details, making it easier to debug deployment issues and understand why a service worker update didn't succeed.

These improvements enhance the reliability and debuggability of Angular's service worker implementation, making it easier for developers to diagnose and resolve issues in production applications.

TypeScript 5.9 support

Angular v20.2 now supports TypeScript 5.9. This means you'll be able to use the latest version of TypeScript in your Angular applications. You can check out the TypeScript 5.9 release notes to learn more about the new features.

Devtools

A new tab in the devtools allows to see the "transfer state" of your application if you use hydration.

Angular CLI

Vitest support

The experimental unit-test builder introduced in Angular v20 has been improved and now supports the headless mode for Vitest, which means that you can run your tests in a headless browser. To do so, you need to use the browser mode of the builder and add the "Headless" suffix to the browser name, like this:

"runner": "vitest",
"browsers": ["ChromiumHeadless"]

Rolldown chunk optimization

The experimental chunk optimization that has been around since Angular v18.1 now uses rolldown instead of rollup. Rolldown is a new tool, compatible with rollup, but written in Rust, which makes it faster. It is developed by the VoidZero team, the same team that now develops Vite. Rolldown is still experimental and less battle-tested than rollup, but as the chunk optimization option is itself experimental, I guess this is not a big deal, and we may see a tiny performance improvement in build times.

AI

A large part of the new features of the CLI are in the AI domain.

A new command lets you generate configuration files for your favorites AI tools:

ng g ai-config --tool=<your-favorite-ai-tool>

The supported tools are gemini, claude, copilot, windsurf, jetbrains, and cursor. For example --tool=claude generates a .claude/CLAUDE.md file in the directory containing the Angular best practices. ng new will also prompt for the AI tool to use when generating a new project.

The MCP server introduced in Angular v20.1 gained a few new features:

  • a search_documentation tool has been added to search the documentation from the CLI, using the same search engine (Algolia) as the documentation website. The LLM will use this if asked a question about Angular and return a list of entries with their titles and URLs, with the top entry directly fetched and displayed;
  • a get_best_practices tool, similar to the existing resource, in case resources are not available in your LLM;

The mcp command now also accepts options:

  • --local-only indicates to only use local tools (no network access, so the search_documentation is not available)
  • --read-only indicates to only use read-only tools (no write access, but for the moment all tools are read-only)
  • --experimental-tool <tool> indicates to use an experimental tool

Two new experimental tool have been added, and can be enable with --experimental-tool:

  • a modernize tool, which helps you generate code or update your files to use the latest best practices and features. When used, it will tell the LLM which migration can be run and the LLM can determine which ones are useful and can then prompt you to run the migration. The current migrations listed are control-flow, self-closing-tags, inject, standalone, and the signal migrations (input, output, queries).
  • a find_examples tool, which helps the LLM to generate code by using a pool of examples. Only one simple example is available for now (a simple @if) but the feature allows to register your own examples in a directory, and then use the NG_MCP_EXAMPLES_DIR environment variable to let the MCP know about the location of the examples.

This next release will be v21, and will hopefully include signal forms. 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 20.1?

Angular 20.1.0 is here!

Angular logo

One month after the huge v20 release, the Angular team delivers a new minor update. This release introduces devtools with signal dependency graphs, improvements in component testing with new binding helpers, and adds a lot of HTTP client options that can boost your Core Web Vitals scores. Plus, Angular takes its first steps into the AI-assisted development era with MCP server support.

Let's dive in.

Devtools

The devtools have been updated with some nice new features. You can now inspect the details of a route in the router tab, but most of the improvements are for signals.

We can now inspect a signal from the devtools and jump to its definition. But more importantly, we now have a new "Signal Graph" tab that shows the dependencies between signals in a graph, and flashing of nodes when a signal changes. It can be enabled in the devtools settings by switching on the "Enable signal graph (experimental)" option. You can then click on a component and on the "Show signal graph" button to see the graph of signals that are used in this component!

You then get a (still a bit rough) graph with signals and inputs in blue, computed signals in green, and effects in grey. It will probably be improved in the future, but it is already a nice way to visualize the dependencies between signals and to understand how they are used in your application. Some compiler magic (a TypeScript transformer) has been implemented to add a "debugName" to each signal, based on the variable name in the component, which is used in the graph to display the name of the signal.

Tests

It is now possible to create a component in a test with its bindings directly set.

Let's say you have a User component:

@Component({
  // ...
})
export class User {
  readonly userModel = input.required<UserModel>();
  readonly selected = output<true>();
  // ...

A common practice is to test this type of component with a wrapper component:

@Component({
  // ...
  template: `<app-user
    [userModel]="userModel()"
    (selected)="isSelected.set(true)"
  />`,
})
export class UserHost {
  readonly userModel = signal<UserModel>({
    /* ... */
  });
  readonly isSelected = signal(false);
}

// ...
it("should display a user", () => {
  const fixture = TestBed.createComponent(UserHost);
  // ....
});

This can now be simplified, thanks to inputBinding(), outputBinding() and twoWayBinding() that were introduced in v20 and to an evolution of the TestBed.createComponent method in v20.1:

it("should display a user", () => {
  const userModel = signal<UserModel>({
    /* ... */
  });
  const isSelected = signal(false);
  const fixture = TestBed.createComponent(User, {
    // 👇 directly bind inputs/outputs
    bindings: [
      inputBinding("userModel", userModel),
      outputBinding("selected", () => isSelected.set(true)),
    ],
  });
  // ....
});

This is a nice improvement, even if the "wrapper component" approach is still valid and can be useful in some cases, for example, when you want to test a component which uses ng-content or a directive that requires a host element.

Performances

The low-level set of instructions that the compiler generates for templates has been extended with DOM-only instructions, used when the compiler can safely determine that the element has no directives applied to it. Those DOM-only instructions are more efficient than the generic ones, even if this is hard to measure in practice.

A template like the following:

<figure>
  <img [src]="userImageUrl()" />
  <figcaption>{{ userModel().name }}</figcaption>
</figure>

used to be compiled into the following instructions:

if (renderFlags & RenderFlags.Create) {
  elementStart(0, "figure");
  {
    element(1, "img");
  }
  {
    elementStart(2, "figcaption");
    text(3);
    elementEnd();
  }
  elementEnd();
}
if (renderFlags & RenderFlags.Update) {
  advance();
  property("src", ctx.userImageUrl());
  advance(2);
  textInterpolate(ctx.userModel().name);
}

It is now compiled into the following instructions:

if (renderFlags & RenderFlags.Create) {
  domElementStart(0, "figure");
  {
    domElement(1, "img");
  }
  {
    domElementStart(2, "figcaption");
    text(3);
    domElementEnd();
  }
  domElementEnd();
}
if (renderFlags & RenderFlags.Update) {
  advance();
  domProperty("src", ctx.userImageUrl());
  advance(2);
  textInterpolate(ctx.userModel().name);
}

The elementStart, element, elementEnd, and property instructions still exist, but they are now used only when the component has dependencies.

Templates

The compiler now supports assignment operators in templates: +=-=*=/=%=**=&&=||= and ??= are now allowed.

The NgOptimizedImage directive now offers a decoding option, which can be set to async, sync, or auto (default). You can use async to decode the image off the main thread, or sync to decode it immediately (blocking). auto lets the browser decide the best strategy.

HTTP

The HttpClient gained a bunch of new options in v20.1:

  • timeout which indicates the maximum time to wait for a response. It can be set to a number of milliseconds. If the request takes longer than this time, it will be aborted.
  • cache (with the Fetch API only) which indicates if the request should use the browser cache. It can be set to default, no-store, reload, no-cache, force-cache or only-if-cached, see MDN for more details.
  • priority (with the Fetch API only) which indicates the priority of the request. It can be set to auto (default), high or low. It can be useful to improve your Core Web Vitals scores by distinguishing between high-priority requests that impact LCP and low-priority requests.
  • mode (with the Fetch API only) which indicates the mode of the request. It can be set to cors (default), no-cors, same-origin or navigate. This is used to determine if cross-origin requests lead to valid responses, and which properties of the response are readable, see MDN.
  • redirect (with the Fetch API only) which indicates how to handle redirects. It can be set to follow (default), error or manual.
  • credentials (with the Fetch API only) which indicates whether to include credentials in the request. It can be set to omit (default), same-origin or include. This is used to determine if cookies and HTTP authentication are included in the request, see MDN. This option is more flexible than the existing withCredentials option (which is still available and is equivalent to include) and should be preferred.
  • referrer (with the Fetch API only) which indicates the referrer of the request. It can be set to a URL, an empty string for no referrer or about:client. This is used to determine the referrer of the request, see MDN.
  • integrity (with the Fetch API only) which indicates the integrity of the request. It can be set to a hash of the response body. This is used to verify that the response has not been tampered with, see MDN.

Most of these options are not supported when using the XHR backend.

The HttpResource also supports all these options as well as keepalive (introduced in v20 for the HttpClient).

Router

The router loadChildren and loadComponent functions now run in the injection context of the route, allowing you to inject services within the functions.

Service worker

The Service Worker Push service SwPush now supports the notificationclose and the pushsubscriptionchange events, with the new notificationCloses and pushSubscriptionChanges observables (in addition of the existing notificationClicks observable).

Angular CLI

This is a fairly small release for the CLI.

A notable feature is the possibility to use base64 and dataurl loaders, in addition to the pre-existing text, binary, and file loaders. dataurl inlines the content as a data URL and base64 inlines the content as a Base64-encoded string.

This can be used to inline small assets in your application:

import contents from './assets/small-image.png' with { loader: 'dataurl' };

The experimental unit-test builder now allows to define some options (only for vitest):

  • setupFiles which are files that are loaded before the tests are run, allowing you to set up the test environment.
  • codeCoverageReporters which allows you to specify the code coverage reporters to use.

But the main feature of CLI v20.1 is the new ng mcp command, which allows you to start an MCP server, a hot topic in the AI community.

So let's talk about AI!

AI

Angular is fully embracing the AI trend. We now have a new documentation page explaining how to properly configure your favorite AI tool to help you with Angular development: Develop with AI. The page provides rules files for the most popular AI tools, such as Copilot, Cursor, VSCode, etc.

The CLI has also been updated and now includes an MCP server. MCP stands for "Model Control Protocol" and is the hot new protocol that allows LLMs to communicate with external tools and resources. This protocol was introduced by Anthropic last year and is now supported by many AI tools.

You can run:

ng mcp

which outputs the configuration you need to add to your AI tool:

To start using the Angular CLI MCP Server, add this configuration to your host:

{
  "mcpServers": {
    "angular-cli": {
      "command": "npx",
      "args": ["@angular/cli", "mcp"]
    }
  }
}

Exact configuration may differ depending on the host.

This means that you can now use the CLI to start an MCP server, then configure your favorite AI tool to connect to it. The MCP server is quite minimalist for now, as it offers only one resource (the best practice file mentioned above) and one tool (the ability to list the Angular projects and their options in your workspace).

In my experiment with Copilot and Claude Code, it seems that extending the context with the resources the MCP server provides does make a difference in the quality of the suggestions.

Summary

Most features of v20.1 are oriented towards improving the developer experience. We also saw a new RFC about the future of the animation package. We can expect movements in this area in the next releases and hopefully some movements in the signal forms area as well.

Stay tuned!

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