Angular introduced a major feature in the v16-next release โ Signals. This feature represents a huge leap for the framework and could, in my opinion, significantly boost Angular's popularity in the coming years. Why? Because it finally addresses a long-standing limitation: Zone.js.
Let's dive into the backstory behind this shift and explore what makes Signals such a game-changer.
๐ง Understanding Angular's Current Change Detection: Zone.js
To appreciate why Signals are a big deal, we need to understand how Angular has traditionally handled reactivity โ via Zone.js.
What is Zone.js?
Zone.js is Angular's default mechanism for detecting changes in your app. Whenever an event (like a click or a timer) occurs, it's the browser โ not Angular โ that triggers it. Zone.js steps in by monkey-patching standard JavaScript APIs (like window, document, and even setTimeout) to intercept these browser events. Once intercepted, Angular kicks off a change detection cycle to update the DOM.
Why is Zone.js problematic?
While Zone.js has been reliable and powerful, it comes with several limitations:
- It only knows something might have changed, not what exactly changed or where.
- Because of this uncertainty, Angular often rechecks the entire component tree โ which can be overkill for large applications.
- Debugging is harder since monkey-patching masks native behavior.
- Native JavaScript features like
async/awaitcan't be monkey-patched, so workarounds are needed. - Performance becomes an issue in large-scale apps as the app grows and change detection becomes more expensive.
Some existing strategies like OnPush change detection and pure pipes help, but they don't solve the root problem.
๐ฎ Enter Signals: Angular's Future of Reactivity
This is where Signals come into play โ a modern reactivity model designed to give Angular fine-grained control over updates.
What's a Signal?
A Signal is essentially a reactive wrapper around a value. When the value changes, it notifies only those who care โ the consumers โ rather than rechecking everything.
There are two main types:
- Writable Signals โ You update them directly via a mutation API.
- Computed Signals โ Their value is derived from other signals and updates automatically when dependencies change.
โ Writable Signals (Direct Mutation)
A writable signal holds a value that can be directly updated using the set() or update() methods. It's similar to using a BehaviorSubject in RxJS but with Angular's reactivity baked in.
import { Component, signal } from '@angular/core';
@Component({
selector: 'counter-component',
standalone: true,
template: `
<h2>Counter: {{ count() }}</h2>
<button (click)="increment()">Increment</button>
`,
})
export class CounterComponent {
// Writable signal
count = signal(0);
increment() {
this.count.update((current) => current + 1);
}
}๐ What's happening?
countis a signal holding a number.- The template uses
count()to read the signal. - When the button is clicked,
update()changes the value, and only the parts of the UI that readcount()are updated.
๐ Computed Signals (Derived Values)
A computed signal automatically recalculates its value whenever any signal it depends on changes. You don't mutate it directly โ it's derived. Let's look at a simple example that the Angular team shared โ converting Celsius to Fahrenheit:
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'temperature-converter',
standalone: true,
template: `
<label>
Celsius:
<input type="number" [value]="celsius()" (input)="onInput($event)" />
</label>
<p>Fahrenheit: {{ fahrenheit() }}</p>
`,
})
export class TemperatureConverterComponent {
// Writable signal
celsius = signal(0);
// Computed signal
fahrenheit = computed(() => this.celsius() * 1.8 + 32);
onInput(event: Event) {
const value = +(event.target as HTMLInputElement).value;
this.celsius.set(value);
}
}Notice how the Fahrenheit value automatically recalculates when Celsius changes โ with no manual wiring needed!
๐ What's happening?
celsiusis a writable signal tied to user input.fahrenheitis a computed signal that recalculates only whencelsiuschanges.- Angular automatically tracks dependencies and re-runs
computed()only when needed.
โ๏ธ Why Signals Matter
Signals aren't just syntactic sugar. They represent a philosophical shift in how Angular handles reactivity:
- Targeted updates: Only consumers that rely on changed signals get updated.
- Automatic dependency tracking: Signals know who uses them, so changes ripple only where needed.
- No more full tree checks: This drastically improves performance, especially in complex apps.
- Lazy evaluation: Computed signals only recompute when accessed.
- Interop with RxJS and legacy code: You can mix signals with the current system, easing migration.
๐ฐ The Learning Curve
Yes โ this will be a shift for many developers.
We'll have new concepts to learn:
- Signal-based inputs and outputs
- Effects instead of lifecycle hooks
- Possibly fewer decorators
- A whole new way to think about state and reactivity
It might sound overwhelming, but like any meaningful evolution, it comes with a learning curve. Still, the payoff is worth it: more predictable change detection, better performance, and cleaner state management.
๐ The Road Ahead
This post was meant to give you a high-level overview of what Angular Signals bring to the table. You can find more detailed examples in Angular docs. Thanks for reading and happy coding!
Thank you for being a part of the community
Before you go:
- Be sure to clap and follow the writer ๏ธ๐๏ธ๏ธ
- Follow us: X | LinkedIn | YouTube | Newsletter | Podcast | Differ | Twitch
- Start your own free AI-powered blog on Differ ๐
- Join our content creators community on Discord ๐ง๐ปโ๐ป
- For more content, visit plainenglish.io + stackademic.com