Angular Signals
A signal is a value that tells Angular when it changes, so the screen can update exactly where that value is used.
Creating a Signal
Import signal from @angular/core and give it a starting value:
Example
import { Component, signal } from "@angular/core";
@Component({
selector: "app-water",
template: `
<p>Glasses of water today: {{ glasses() }}</p>
<button (click)="drink()">Drink one</button>
<button (click)="reset()">Reset</button>
`,
})
export class Water {
glasses = signal(0);
drink() {
this.glasses.update((g) => g + 1);
}
reset() {
this.glasses.set(0);
}
}Glasses of water today: 0 (after 3 clicks on Drink one) Glasses of water today: 3
To read a signal, call it like a function: glasses(). This works in templates and in TypeScript code.
Changing a Signal
| Method | Use it when | Example |
|---|---|---|
set(value) | You know the new value | name.set("Ayesha") |
update(fn) | The new value depends on the old one | count.update(c => c + 1) |
Signals with Objects and Arrays
Treat objects and arrays in signals as read-only. Create a new one when you change something:
Example
import { Component, signal } from "@angular/core";
interface Item {
id: number;
name: string;
}
@Component({
selector: "app-shopping",
template: `
<button (click)="add()">Add Atta</button>
<ul>
@for (item of items(); track item.id) {
<li>{{ item.name }}</li>
}
</ul>
`,
})
export class Shopping {
items = signal<Item[]>([{ id: 1, name: "Rice" }]);
add() {
this.items.update((list) => [...list, { id: Date.now(), name: "Atta" }]);
}
}If you push into the same array, the signal does not know anything changed.
computed()
A computed signal gets its value from other signals. Angular recalculates it only when those signals change.
Example
import { Component, signal, computed } from "@angular/core";
@Component({
selector: "app-fee",
template: `
<p>Months: {{ months() }}</p>
<p>Total fee: Rs {{ total() }}</p>
<p>{{ discountNote() }}</p>
<button (click)="months.update((m) => m + 1)">Add a month</button>
`,
})
export class Fee {
months = signal(2);
monthlyFee = signal(3000);
total = computed(() => this.months() * this.monthlyFee());
discountNote = computed(() =>
this.months() >= 3 ? "You get 10% off!" : "Join for 3 months to get a discount."
);
}Months: 2 Total fee: Rs 6000 Join for 3 months to get a discount. (after 1 click) Months: 3 Total fee: Rs 9000 You get 10% off!
Computed signals are read-only. You cannot call set on them.
effect()
An effect runs code whenever the signals it reads change. Use it for side effects, such as saving to local storage or logging.
Example
import { Component, signal, effect } from "@angular/core";
@Component({
selector: "app-theme",
template: `<button (click)="toggle()">Theme: {{ theme() }}</button>`,
})
export class Theme {
theme = signal("light");
constructor() {
effect(() => {
console.log("Theme is now", this.theme());
localStorage.setItem("theme", this.theme());
});
}
toggle() {
this.theme.update((t) => (t === "light" ? "dark" : "light"));
}
}Theme is now light Theme is now dark
Effects must be created in an injection context, such as the constructor.
Do not use effect() to copy one signal into another. If a value can be calculated, use computed() instead. Effects are for talking to the outside world.
Why Signals?
- Angular knows exactly which parts of the page depend on which values
- Your code is simple: read with
(), write withsetorupdate - Signals work everywhere: components, services and inputs (
input()returns a signal)
Exercise
Exercise
How do you read the current value of a signal called price?
