DEV Community

Cover image for Angular Signals + Zoneless Change Detection — The Biggest Performance Upgrade in 2026
mhmoud ashour
mhmoud ashour

Posted on

Angular Signals + Zoneless Change Detection — The Biggest Performance Upgrade in 2026

Angular just changed everything about how reactivity works.

If you’re still using Zone.js and ChangeDetectionStrategy.OnPush to optimize performance — there’s a better way in 2026.

Angular Signals + Zoneless Change Detection is the biggest performance upgrade Angular has ever shipped.

Here’s everything you need to know.


What Are Angular Signals?

A Signal is a reactive value that automatically notifies Angular when it changes — without Zone.js watching everything.

import { signal, computed, effect } from '@angular/core';

// Create a signal
const count = signal(0);

// Read it
console.log(count()); // 0

// Update it
count.set(1);
count.update(val => val + 1);

// Computed signal — updates automatically
const doubled = computed(() => count() * 2);

// Effect — runs when signals change
effect(() => {
  console.log('Count changed:', count());
});
Enter fullscreen mode Exit fullscreen mode

Simple. Powerful. Zero boilerplate.


The Problem With Zone.js

Before Signals, Angular used Zone.js to detect changes. Every time anything happened — a click, an HTTP request, a setTimeout — Zone.js triggered change detection across your entire app.

This is why Angular apps got slow at scale.

// Old way — Zone.js watches everything
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush // needed for performance
})
export class OldComponent {
  count = 0;

  increment() {
    this.count++; // Zone.js detects this
  }
}
Enter fullscreen mode Exit fullscreen mode

The problem? Zone.js can’t know which component actually changed. So it checks everything.


The New Way — Signals + Zoneless

// New way — Signals tell Angular exactly what changed
@Component({
  template: `
    <p>Count: {{ count() }}</p>
    <p>Doubled: {{ doubled() }}</p>
    <button (click)="increment()">+1</button>
  `
})
export class NewComponent {
  count = signal(0);
  doubled = computed(() => this.count() * 2);

  increment() {
    this.count.update(v => v + 1);
  }
}
Enter fullscreen mode Exit fullscreen mode

Angular now knows exactly which component needs to re-render. No more checking everything.


How to Enable Zoneless Change Detection

In Angular 21, you can go fully zoneless:

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { provideExperimentalZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, {
  providers: [
    provideExperimentalZonelessChangeDetection()
  ]
});
Enter fullscreen mode Exit fullscreen mode

Then remove Zone.js from your polyfills:

// angular.json — remove this line
"polyfills": ["zone.js"] // ❌ Remove

// After
"polyfills": [] // ✅ Clean
Enter fullscreen mode Exit fullscreen mode

Real Performance Difference

Here’s what you gain going zoneless with Signals:

Metric Zone.js Signals + Zoneless
Change detection scope Entire app Only changed components
Bundle size +33KB (zone.js) 0KB overhead
Initial render Slower Up to 45% faster
Memory usage Higher Lower
Large list performance Degrades Stays fast

Signal Inputs — The New @Input()

Angular 21 replaces @Input() with signal inputs:

// Old way
@Component({})
export class CardComponent {
  @Input() title: string = '';
  @Input() count: number = 0;
}

// New way — Signal inputs
@Component({
  template: `
    <h2>{{ title() }}</h2>
    <p>{{ count() }}</p>
  `
})
export class CardComponent {
  title = input<string>('');
  count = input<number>(0);

  // Computed from inputs
  summary = computed(() =>
    `${this.title()} has ${this.count()} items`
  );
}
Enter fullscreen mode Exit fullscreen mode

Signal inputs are readonly — no accidental mutations. And they work perfectly with computed signals.


Signal Outputs — The New @Output()

// Old way
@Component({})
export class ButtonComponent {
  @Output() clicked = new EventEmitter<void>();
}

// New way
@Component({
  template: `<button (click)="handleClick()">Click me</button>`
})
export class ButtonComponent {
  clicked = output<void>();

  handleClick() {
    this.clicked.emit();
  }
}
Enter fullscreen mode Exit fullscreen mode

Model Signals — Two-Way Binding

Angular 21 introduces model() for two-way binding:

@Component({
  template: `
    <input [value]="name()" (input)="name.set($event.target.value)" />
    <p>Hello, {{ name() }}!</p>
  `
})
export class FormComponent {
  name = model('');
}
Enter fullscreen mode Exit fullscreen mode

Migration Strategy

Don’t rewrite everything at once. Follow this order:

Step 1 — Start using signal() for local component state
Step 2 — Replace @Input() with input()
Step 3 — Replace @Output() with output()
Step 4 — Replace services with signalStore() (NgRx Signals)
Step 5 — Enable zoneless change detection

Each step is independent — you can migrate gradually. ✅


Common Mistakes to Avoid

❌ Reading signals outside reactive context:

// Wrong — won't track changes
ngOnInit() {
  const value = this.count(); // reads once, not reactive
}

// Right — use effect() for side effects
effect(() => {
  console.log(this.count()); // tracks changes
});
Enter fullscreen mode Exit fullscreen mode

❌ Mutating signal values directly:

// Wrong
const items = signal([1, 2, 3]);
items().push(4); // doesn't trigger update!

// Right
items.update(arr => [...arr, 4]);
Enter fullscreen mode Exit fullscreen mode

❌ Creating signals inside loops or conditions:

// Wrong
if (someCondition) {
  const signal = signal(0); // unstable
}

// Right — always at component level
mySignal = signal(0);
Enter fullscreen mode Exit fullscreen mode

Want a Production-Ready Angular 21 Setup?

I built NgMFE Starter Kit — a complete Angular 21 Micro-Frontend boilerplate with Signals and NgRx Signals pre-configured:

  • ⚡ Angular 21 + Native Federation
  • 📡 NgRx Signals for state management
  • 🔐 JWT Auth + Route Guards
  • 🌍 Arabic RTL support
  • 🌙 Dark/Light mode
  • 🚀 CI/CD with GitHub Actions + Vercel
  • ✅ 13 unit tests passing

🔴 Live Demo: https://ng-mfe-shell.vercel.app
(login: admin/admin)

🛒 Get the kit:
👉 https://mhmoudashour.gumroad.com/l/hdditr

💼 Need it set up for your project?
👉 https://www.upwork.com/services/product/development-it-set-up-angular-micro-frontend-architecture-for-your-enterprise-app-2037100004401414520?ref=project_share


Have questions about Angular Signals or the migration? Drop them in the comments! 🙏

Top comments (0)