Angular Signals
A fine-grained reactivity primitive for state that automatically updates the view whenever it changes.
2 phút đọc
Historically, Angular relied on a mechanism called Zone.js to detect when something might have changed and re-check the entire component tree. Signals are a newer, more precise alternative: a signal is a wrapper around a value that knows exactly which parts of the app read it, so Angular can update only what actually depends on it.
Creating and reading a signal
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-counter',
template: `
<p>Count: {{ count() }}</p>
<button (click)="increment()">+1</button>
`,
})
export class CounterComponent {
count = signal(0);
increment(): void {
this.count.set(this.count() + 1);
}
}signal(0) creates a signal holding the number 0. Reading its value means calling it — count(), not count — and updating it goes through .set() (replace the value) or .update() (compute a new value from the old one, e.g. this.count.update((c) => c + 1)). The template calls count() too, and Angular tracks that read, so when count changes, only the pieces of the template that actually depend on it are refreshed.
Derived state with computed()
import { Component, signal, computed } from '@angular/core';
@Component({
selector: 'app-cart-summary',
template: `
<p>Items: {{ itemCount() }}</p>
<p>Total: {{ total() | currency }}</p>
`,
})
export class CartSummaryComponent {
prices = signal([19.99, 34.5, 12.0]);
itemCount = computed(() => this.prices().length);
total = computed(() => this.prices().reduce((sum, p) => sum + p, 0));
}computed() derives a new signal from one or more existing ones. It's lazy and cached — it only recalculates when a signal it actually read (prices, here) changes, not on every change-detection cycle — which makes it a cheap, declarative alternative to recalculating a value manually inside ngOnChanges or a getter.
Running side effects with effect()
import { Component, signal, effect } from '@angular/core';
@Component({ selector: 'app-theme-toggle', template: `...` })
export class ThemeToggleComponent {
isDarkMode = signal(false);
constructor() {
effect(() => {
document.body.classList.toggle('dark', this.isDarkMode());
});
}
}effect() runs a function once immediately and again every time a signal it reads changes — useful for side effects outside Angular's own rendering, like syncing to localStorage or, as above, toggling a class on document.body. Effects should be used sparingly; most reactive logic is better expressed as a computed() value than a side effect, since a computed value is easier to trace and test.
Why this matters
Signals let Angular know precisely what changed and what depends on it, rather than re-checking the whole component tree on every event. That's a meaningful performance improvement in larger apps, but the more immediate benefit while learning Angular is clarity: a signal makes reactive state an explicit, visible part of a component's code, rather than something implicit that Angular manages behind the scenes through Zone.js.