Zoneless Angular 20 Explained: How to Remove Zone.js and Migrate

Geetha - Zoneless Angular (1)

Introduction: Why Change Detection Matters

If you’ve ever wondered why Angular sometimes feels too eager to update the DOM, or why debugging random change detection cycles can be tricky, you’re not alone. Until now, Angular has relied on Zone.js to manage change detection automatically.

But with Angular 20, we enter a new era: Zoneless Angular. This feature removes the dependency on Zone.js, giving developers smaller bundles, better performance, and more control.

In this article, you’ll learn:

  • How change detection worked before
  • Why Zone.js caused overhead
  • How Signals replace global detection
  • How to migrate safely
  • Best practices for zoneless applications

How Angular Used to Work (With Zone.js)

Zone.js in Action

Before Angular 20, Zone.js was essentially Angular’s “change detection engine trigger.”
It worked by monkey-patching core browser APIs — meaning it would override native APIs like:

  • setTimeout, setInterval
  • Promise.then
  • XMLHttpRequest / fetch
  • DOM events (click, input, etc.)

Whenever these async operations finished, Zone.js would automatically notify Angular that “something might have changed.” Angular would then run change detection for the entire component tree, ensuring the UI was always up to date.

Example: Angular Before v20 (With Zone.js)

// app/counter.component.ts (Angular pre-v20) 
@Component({ 
  selector: 'app-counter', 
  template: ` 
    <p>Count: {{ count }}</p> 
    <button (click)="increment()">Increment</button> 
  ` 
}) 
export class CounterComponent { 
  count = 0; 
 
  increment() { 
    this.count++; // Zone.js auto-triggers change detection 
  } 
}

Without Zone.js, Angular would not automatically detect that count changed.

You would need:

this.cdRef.detectChanges();

Simplified Flow (With Zone.js)

User Click
  ↓
Browser Event
  ↓
Zone.js intercepts
  ↓
Angular runs global change detection
  ↓
DOM updates

Problems With Zone.js

  • Bundle Size: Zone.js adds ~100KB.
  • Performance Overhead: Every async operation was monitored.
  • Unpredictability: Change detection could fire when you didn’t expect it.
  • Debugging Complexity: Hard to know why a component re-rendered.
  • Memory Leaks: Zones sometimes retained references too long.

Zone.js made Angular convenient—but not predictable.

Angular 20: The Zoneless Revolution

With Angular 20, Zone.js is no longer a dependency. Instead of monkey-patching all browser APIs, Angular embraces fine-grained reactivity and manual change detection when needed. This gives developers performance, predictability, and control without losing the ergonomic developer experience.

Signals: Built-in Reactivity

Angular 20 introduces signals as a first-class reactive primitive (similar to what you see in SolidJS or Vue’s reactivity system).

When a signal changes, Angular updates only the parts of the template that depend on it.

Example: Angular 20+ with Signals

// app/counter.component.ts (Angular 20+) 
@Component({ 
  selector: 'app-counter', 
  template: ` 
    <p>Count: {{ count() }}</p> 
    <button (click)="increment()">Increment</button> 
  ` 
}) 
export class CounterComponent { 
  count = signal(0);  // reactive state 
 
  increment() { 
    this.count.update(c => c + 1);  
    // Any template that reads count() auto-updates,  
    // no Zone.js patching required 
  } 
}

Key points:

  • signal(0) creates a reactive value.
  • You read signals with count() instead of count.
  • When updated, Angular knows exactly what to update in the DOM — no global change detection sweep.
  • This makes UI updates surgical and efficient, instead of “check everything on the page.”

Explicit Control with Change Detection

There are still cases where you need manual control — for example, when dealing with external async APIs, legacy libraries, or non-signal-based updates.

Example: Manual Change Detection

@Component({ 
  selector: 'app-data', 
  changeDetection: ChangeDetectionStrategy.OnPush 
}) 
export class DataComponent { 
  data: any; 
 
  constructor(private http: HttpClient, private cdr: ChangeDetectorRef) {} 
 
  async fetchData() { 
    this.data = await this.http.get('/api/data').toPromise(); 
    this.cdr.detectChanges();  
    // Manually tell Angular: "I’ve got new data, update the DOM now" 
  } 
}

Key points:

  • ChangeDetectionStrategy.OnPush means Angular will only update when you tell it to, or when inputs change.
  • ChangeDetectorRef.detectChanges() is now the manual trigger instead of Zones.

Zone.js vs Zoneless Angular (Comparison)

Screenshot 2026-02-25 152918

Benefits of Zoneless Angular

1. Smaller Bundle Size

# Angular 19 with Zone.js 
ng build --prod → ~500KB 
 
# Angular 20 Zoneless 
ng build --prod → ~400KB

That’s around a 20% reduction in many apps.

2. Faster and More Predictable Performance

  • No unnecessary cycles.
  • Lower memory usage.
  • Faster startup times.

3. More Control for Developers

You decide when change detection should run:

  • Use signals for automatic reactive updates.
  • Use ChangeDetectorRef for manual control.

4. Better Debugging

You’ll know exactly when and why a component updates—making performance tuning much simpler.

Migrating to Zoneless Angular

Step 1: Update to Angular 20

ng update @angular/core@20 @angular/cli@20

Step 2: Remove Zone.js

// package.json 
"dependencies": { 
  // Remove this 
  // "zone.js": "^0.13.0" 
}

Step 3: Update main.ts

Before (with Zone.js)

import 'zone.js'; 
import { bootstrapApplication } from '@angular/platform-browser'; 
import { AppComponent } from './app/app.component'; 
 
bootstrapApplication(AppComponent);

After (Zoneless)

import { bootstrapApplication } from '@angular/platform-browser'; 
import { AppComponent } from './app/app.component'; 
 
bootstrapApplication(AppComponent);  

// No Zone.js import needed

Step 4: Refactor Components

Option 1: Use Signals (Recommended)

@Component({ 
  selector: 'app-user-list', 
  template: ` 
    <div *ngFor="let user of users()"> 
      {{ user.name }} - {{ user.email }} 
    </div> 
  ` 
}) 
export class UserListComponent { 
  users = signal<User[]>([]); 
 
  async ngOnInit() { 
    const data = await this.userService.getUsers().toPromise(); 
    this.users.set(data); 
  } 
}

Option 2: Manual Control

@Component({ 
  selector: 'app-data-table', 
  changeDetection: ChangeDetectionStrategy.OnPush 
}) 
export class DataTableComponent { 
  data: any[] = []; 
 
  constructor(private cdr: ChangeDetectorRef) {} 
 
  async refreshData() { 
    this.data = await this.dataService.fetchData().toPromise(); 
    this.cdr.detectChanges(); 
  } 
}

Best Practices for Zoneless Angular

1. Use signals for reactive state

user = signal<User | null>(null); 
isLoading = signal(false); 
 
displayName = computed(() => { 
  const u = this.user(); 
  return u ? `${u.firstName} ${u.lastName}` : 'Guest'; 
});

2. Use OnPush by Default

Improves predictability and performance.

3. Be Explicit with Async Work

  • Use signals
  • Or call detectChanges()

4. Test Async Flows Carefully

Change detection is no longer implicit.

Common Pitfalls

Problem: No UI Update

setTimeout(() => { 
  this.value = 'updated'; // UI won’t refresh 
}, 1000);

Fix: Manual trigger

setTimeout(() => { 
  this.value = 'updated'; 
  this.cdr.detectChanges(); 
}, 1000);

Or use signals

this.value.set('updated');

What’s Next for Angular Change Detection

  • Stronger Signal APIs for more expressive reactive programming.
  • Compiler-level optimizations for tree-shaking and dead code elimination.
  • Built-in profiling tools for change detection performance.
  • Hybrid approaches combining automatic + manual control for flexibility.

Conclusion: Why This Matters

Angular 20 removes Zone.js and replaces global change detection with:

  • Targeted reactive updates
  • Explicit developer control
  • More predictable rendering

If you’re starting a new Angular application, consider going zoneless from day one.

If you’re migrating an existing app:

  • Introduce signals gradually
  • Adopt OnPush
  • Remove Zone.js incrementally
  • Measure performance impact

Zoneless Angular represents a shift toward modern, explicit reactivity — without sacrificing developer ergonomics.

Tags: