Derive State & React to Changes
computed() & effect()
computed() builds a signal from other signals; effect() runs code whenever its dependencies change.
What you'll learn
- Build a computed signal from other signals
- Use effect() for side effects
- Convert an Observable to a signal with toSignal
signal() gives you a writable value. computed() derives a read-only value from other signals. effect() runs code whenever the signals it touches change. Together they give you a tiny reactive runtime — no RxJS required.
computed — derived state
import { Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-profile',
standalone: true,
template: `<p>{{ fullName() }}</p>`,
})
export class ProfileComponent {
first = signal('Ada');
last = signal('Lovelace');
fullName = computed(() => `${this.first()} ${this.last()}`);
} fullName() recomputes only when first or last change — and only when something actually reads it.
effect — side effects
effect() is for cases where reading a signal should trigger something outside the framework — logging, syncing to localStorage, calling an imperative API.
import { Component, effect, signal } from '@angular/core';
@Component({ selector: 'app-counter', standalone: true, template: `{{ count() }}` })
export class CounterComponent {
count = signal(0);
constructor() {
effect(() => {
localStorage.setItem('count', String(this.count()));
});
}
} Effects run after the first render and again whenever their dependencies change. They are cleaned up automatically when the component is destroyed.
Bridge Observables in
toSignal wraps any Observable into a signal — handy for HTTP results or router data.
import { toSignal } from '@angular/core/rxjs-interop';
user = toSignal(this.users.getCurrent(), { initialValue: null });
isLoading = computed(() => this.user() === null); Use signal() for state, computed() for derivations, effect() for side effects — that single mental model carries you through most components.