Angular Signals
Signals are reactive state: read by calling (e.g., count()), update with set()/update(), derive with computed(), and run side effects with effect().
Signals Essentials
- Signal: A value you read by calling it (e.g.,
count()). Updating it notifies dependents. - State: Use
signal()for local component state. - Derived: Use
computed()for read-only formulas. - Effects: Use
effect()to run side effects when dependencies change.
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
const double = computed(() => count() * 2);
effect(() => console.log('count =', count()));
count.update(n => n + 1);
Notes:
- Related: See Data Binding, Services & DI, and Change Detection.
- Use signals for local component state.
- Bridge RxJS with
toSignal()/toObservable()when needed. - Legacy equivalence: For lists, prefer
@forwithtrack; with*ngFor, usetrackByfor the same effect.
Working with Signals
Update a signal with .set() or .update().
Read a signal by calling it like a function.
const count = signal(0);
count.set(1);
count.update(n => n + 1);
console.log(count());
Example
Use a signal, derive with computed(), react with effect(), and update with update():
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Signals</h3>
<p>Count: {{ count() }}</p>
<p>Double: {{ double() }}</p>
<button (click)="inc()">Increment</button>
`
})
export class App {
count = signal(0);
double = computed(() => this.count() * 2);
constructor() {
effect(() => console.log('count changed', this.count()));
}
inc() { this.count.update(n => n + 1); }
}
bootstrapApplication(App);
<app-root></app-root>
Example explained
signal(0): Creates reactive state you read by calling (e.g.,count()).computed(() => ...): Derives a read‑only value (double()) from other signals.effect(() => ...): Runs whenever dependencies change (logs oncount()updates).update(n => n + 1): Writes to the signal and notifies dependents.
Tips:
- Immutable-friendly: Use creating new objects/arrays in
set/updateinstead of mutating in place. - Keep effects small: Avoid writing to signals inside
effect()to prevent feedback loops. - Compute cheaply: Keep
computed()pure and cheap; derive from other signals only.
Derived Values and Effects
- Wrap read-only formulas in
computed(); it recalculates when dependencies change. - Use
effect()for side effects such as logging or syncing. - Keep effects idempotent and light.
const a = signal(2);
const b = signal(3);
const sum = computed(() => a() + b());
effect(() => console.log('sum =', sum()));
Example
Compute a derived value with computed() and observe it with effect():
Example
import { bootstrapApplication } from '@angular/platform-browser';
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-root',
standalone: true,
template: `
<h3>Derived & Effects</h3>
<p>a: {{ a() }} | b: {{ b() }} | sum: {{ sum() }}</p>
<button (click)="incA()">inc a</button>
<button (click)="incB()">inc b</button>
`
})
export class App {
a = signal(2);
b = signal(3);
sum = computed(() => this.a() + this.b());
constructor() { effect(() => console.log('sum =', this.sum())); }
incA() { this.a.update(n => n + 1); }
incB() { this.b.update(n => n + 1); }
}
bootstrapApplication(App);
<app-root></app-root>
Example explained
computed:sum()recalculates whena()orb()change.effect: Reacts tosum()updates (logs on each change).incA()/incB(): Useupdateto increment signals, driving recomputation.
Notes:
- Avoid side-effecty computed: Keep
computed()pure (no async/mutations). - Effect lifecycles: Clean up inside
effect()and avoid unnecessary work on each run. - Interop: Bridge with RxJS using
toSignal()/toObservable()when integrating streams.
RxJS Interop
- Convert an
Observableto a signal withtoSignal()for template-friendly reads. - Convert a signal to an
ObservablewithtoObservable()to integrate with stream APIs.
import { signal, computed, effect, toSignal, toObservable } from '@angular/core';
import { interval, map } from 'rxjs';
// Observable -> Signal
const seconds$ = interval(1000).pipe(map(n => n + 1));
const seconds = toSignal(seconds$, { initialValue: 0 });
// Signal -> Observable
const count = signal(0);
const count$ = toObservable(count);
Notes:
- Initial values: Always provide
initialValuetotoSignal()for SSR and first render. - Ownership: Manage subscriptions on the Observable side;
toSignal()handles teardown automatically.
Signals Quick Reference
- Create:
signal(initial) - Read: call the signal (e.g.,
count()) - Write:
set(value),update(fn) - Derived:
computed(fn) - Effects:
effect(fn) - RxJS interop:
toSignal(observable, { initialValue }),toObservable(signal)