← Back to list

Angular moves to Zoneless and use Signal

From angular v 21+, it moves default to zoneless https://angular.dev/guide/zoneless instead of using ZoneJS. Zoneless change how the way…

Cookie Jen · 2026-06-07 05:36 · 0 claps · 2.6 min read
#angular #zoneless #angular-signals
Open on Medium ↗
Wiki topics: 🌐 · Web Development

Angular moves to Zoneless and use Signal

From angular v 21+, it moves default to zoneless https://angular.dev/guide/zoneless instead of using ZoneJS. Zoneless change how the way “change detection” used to work. The way change detection used to work is when you do something (event or mutate property) in a zone, it would tell Angular to do the change detection and then it would evaluate everything from the root component all the way down to all child components. It didn’t care if things were dirty or not, just updated everything. You mutated a property on the component and when change detection runs, it’s checking everything. However, there is a bug due to upgrade Angular to 21 recently can explain this.


@Component({
  ...
})
export class BcComponent {
  @Input() public items: BC[];

  constructor(private bcService: BcService) {
    bcService
      .getBcs()
      .subscribe(bcs => {
        this.items = bcs;
      });
  }
...
}
<p-bc [model]="items">
  <ng-template #item let-item>
    <a [routerLink]="item.url" tabindex="0" role="link">
      <span (click)="onClicked(item.url);">{{ item.label }}</span>
    </a>
  </ng-template>
</p-bc>

This is easy component which subscribe from service return and assign value to an item array then we expect to see new items in view (front end), however, you don’t see these items in view when landing this page. After you type something (interact with page), makes page dirty, then see the view gets updated. Because in Angular 21, it’s smarter trying to not refresh the views of every single component in the entire tree (which is expensive). It’s trying to only check things that are dirty. But zoneless doesn’t mark the component dirty, so even though you’ve mutated the model, the component doesn’t get marked as dirty and so change detection doesn’t know it needs to check this component, which results in the view does not change in front end when landing page.

Fix approach 1: use markForCheck() and ChangeDetectorRef

Want your Angular component to be compatible with Zoneless? You need to use one of the core APIs to let it know that the change detection needs to refresh this view, few ways can do that, one of those is to use Mark for check and Change detector ref.

export class BcComponent implements OnInit {
  @Input() public items: BC[];

  constructor(private readonly bcService: BcService, 
              private readonly cdr: ChangeDetectorRef) { }

  ngOnInit(): void {
    this.bcService
      .getBcs()
      .subscribe(bcs => {
        this.items = bcs;
        this.cdr.markForCheck();  // mark for check dirty
      });
  }

Fix approach 2: use observable

If we set item as observable, you can use what’s called the “async pipe” in template, And So what this basically means is take this items observable and pipe it to the async pipe, which will automatically handles the subscription to the observable and unsubscription and it will unwrap the value when it gets it. So you don’t need to add .Subscribe()… after getBcs()and don’t need to worry about kill unused component.

export class BcComponent implements OnInit {
  public items: Observable<BC[]>;

  constructor(private readonly bcService: BcService) {
  }
  ngOninit() {
    this.items = bcService.getBcs();
  }
...
<p-bc [model]="items | async">
  <ng-template #item let-item>
    @if (item.url) {
      <a [href]="item.url" tabindex="0" role="link"
       [pTooltip]="item.label || ''" ...

Fix approach 3: use signal

Detailed intro: https://blog.angular-university.io/angular-signals/ Signals are another way that with zoneless, when you use a signal to set a value, it automatically angular tracks all the signals and and knows what templates they’re used in throughout the whole application. So if you change the value of a signal, it’s going to mark the component dirty and the change detection.

Conceptually, it’s basically an object that has a getter so you can get the value, and it has a setter so you can set the value. However, there are some constraints using it, won’t go too detail here.

In code example, you can convert observable to signal type via “toSignal”, then you can assign value to item in constructor. It will load correctly in view.

import { Component, ChangeDetectionStrategy, Signal } from "@angular/core";
import { toSignal } from "@angular/core/rxjs-interop";
...
export class BcComponent {
  public items: Signal<Bc[]>;

  constructor(private readonly bcService: BcService) {
    this.items = toSignal(this.bcService.getBcs(), { initialValue: [] });
...
<p-bc [model]="items()">
  <ng-template #item let-item>
    @if (item.url) {
      <a [href]="item.url" tabindex="0" role="link"

메타데이터
post_id
c5fc49f26e98
slug
angular-moves-to-zoneless-and-use-signal-c5fc49f26e98
url
https://medium.com/@cookie.jen422/angular-moves-to-zoneless-and-use-signal-c5fc49f26e98
canonical_url
https://medium.com/@cookie.jen422/angular-moves-to-zoneless-and-use-signal-c5fc49f26e98
author_url
https://medium.com/@cookie.jen422
status
ok
fetched_at
2026-06-09 15:37:30