How to enable zoneless in Angular apps?

Zoneless is the official default change-detection mode in Angular 21+, but there’s a lot of confusion (and misleading AI-generated content) out there, so I thought I’d clarify what that means and what you need to change on your apps to enable Zoneless change detection.

Because here is the thing: Zoneless is not magical. If you use old practices, Angular won’t detect your changes anymore. In Zoneless, change detection is triggered by one of these scenarios:

  • A component input binding receives a new value
  • An async pipe detects a new value in an Observable that it is subscribed to
  • A Signal value changes
  • Your code calls ChangeDetectorRef.markForCheck() directly

And that’s it. If you subscribe to an Observable in a service or component’s TypeScript code and display that data in a template without using one of the above mechanisms, Zoneless won’t work.

You can read here if you’re confused about what Zoneless actually means.

In other words, if you want to use Zoneless, you have to update your architecture according to the above recommendations, which basically means making most of your components OnPush compatible.

Note that if you use AI tools in your IDE, the Angular 21 MCP server can help with that migration and suggest changes for you.

Once your application is Zoneless-ready, a few steps will allow your app to become fully Zoneless:

  • In angular.json, remove the zone.js pofyfill from the polyfills section:
  • In main.ts, remove any change detection related providers. They’re not needed in Angular 21 as Zoneless is the default. You can keep that code if using an older version of Angular:
  • Uninstall zone.js by running npm uninstall zone.js

And now you have a truly Zoneless Angular application! Note that the official documentation has similar instructions but is more verbose.

If you have any questions about Zoneless, let me know, and I’ll cover those in another article. And if you need a consultant to help you with Zoneless, you know where to find me.

Angular 21 is available!

Angular 21 has been released, and I’m going to divide the new features into different categories, since a lot of the new features are experimental and not recommended for production use yet.

What’s stable and production-ready:

  • Angular’s MCP Server, now with more tools — allowing LLMs to use new Angular features from day one. The most interesting feature is the onpush_zoneless_migration tool. It can analyze your code and provide a plan to migrate your application to OnPush and zoneless change detection.
  • The Angular CLI now uses Vitest as the default test runner. And there’s an experimental migration if you want to migrate your old Jasmine tests: ng g @schematics/angular:refactor-jasmine-vitest
  • Creating a brand new Angular application with the CLI no longer includes zone.js.

What’s experimental or in developer preview, not recommended for production:

  • Signal Forms, providing a new composable and reactive forms experience built on Signals. Here is my short tutorial about the basics of Signal Forms.
  • Angular Aria is launching in Developer Preview, providing headless components built with accessibility as a priority. To install: npm install @angular/aria. It is starting with 8 components:
    • Accordion
    • Combobox
    • Grid
    • Listbox
    • Menu
    • Tabs
    • Toolbar
    • Tree

You can read more with the official v21 blog post here. I will unpack all these new features over the next few weeks in separate posts (Vitest, Aria, etc.).

Aliasing content for projection

We have discussed content projection before, as well as multi-slot content projection, a technique where we can pass multiple pieces of content from one component to another.

For instance, this can be useful when we want to customize a header, body, and footer of a card or a modal component. We need several different templates to pass to that component, and multi-slot content projection is ideal for this purpose (see an example in action on Stackblitz here).

The only downside of such content projection is that it relies on selectors, which can be CSS classes, HTML tag names, or other similar elements. While that works, it’s not a great developer experience as it does not show explicitely when a piece of HTML is meant to be projected.

This is where aliases come into play. If we have the following child component code, expecting two different pieces of projected content:

With aliases, we can now project anything into those slots by using the attribute ngProjectAs in the parent component:

That way, we no longer rely on CSS selectors. We make it explicit that an element is meant to be projected in a given slot. This results in more robust code as it can’t break when CSS or HTML structures change.