Modern Angular: Signals, Standalone Components and SSR · Components, Signals and Data
Signals: computed, effect, linkedSignal and untracked
Model state with signals, derive values with computed, run side effects with effect, reset dependent state with linkedSignal, and read without tracking.
What you'll build
A product picker driven by signals: the list of products is a signal, the selected product is a linkedSignal that resets when the list changes but can still be set by the user, the price total is computed, and an effect saves the selection, reading a logger setting with untracked so it doesn't re-run when that setting changes.
The idea
signal(value): read withs(), write withset/update. Reads inside templates,computedandeffectare tracked automatically.computed: derived, cached, lazily recalculated.effect: side effects (logging, storage, non-Angular APIs) that re-run when the signals they read change; they run asynchronously during change detection, not synchronously onset.linkedSignal: writable state derived from another signal; it resets whenever the source changes.untracked(() => ...): read a signal without creating a dependency.
The code
import { Injector, computed, effect, linkedSignal, signal, untracked } from "@angular/core";
import { TestBed } from "@angular/core/testing";
import { BrowserTestingModule, platformBrowserTesting } from "@angular/platform-browser/testing";
TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting());
const injector = TestBed.inject(Injector);
const products = signal([{ name: "Mug", price: 8.5 }, { name: "Teapot", price: 24 }]);
const quantity = signal(1);
const selected = linkedSignal(() => products()[0]); // resets when products changes
const total = computed(() => {
console.log(" (computing total)");
return selected().price * quantity();
});
const verboseLogs = signal(false);
effect(() => {
const name = selected().name; // tracked
const verbose = untracked(verboseLogs); // not tracked
console.log(`effect: saved selection ${name}${verbose ? " (verbose)" : ""}`);
}, { injector });
console.log("total:", total());
console.log("total again:", total()); // cached
TestBed.tick(); // effects run during change detection
selected.set(products()[1]); // user picks Teapot
quantity.set(2);
console.log("total:", total());
TestBed.tick();
verboseLogs.set(true); // untracked: the effect doesn't re-run
TestBed.tick();
products.set([{ name: "Kettle", price: 31.9 }]); // new list: selection resets
console.log("selected after list change:", selected().name, "total:", total());
TestBed.tick();
Output
(computing total) total: 8.5 total again: 8.5 effect: saved selection Mug (computing total) total: 48 effect: saved selection Teapot (computing total) selected after list change: Kettle total: 63.8 effect: saved selection Kettle (verbose)
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Using
effectto copy one signal into another; usecomputedorlinkedSignal. - Expecting effects to run synchronously after
set; they're scheduled and run during change detection. - Creating effects outside an injection context (no
injector), which throws. - Mutating objects inside a signal (
list().push(x)) instead ofupdatewith a new value; the signal doesn't notice.
Key points
- signal for state, computed for derived values, effect for side effects.
- linkedSignal for writable state that resets with its source.
- untracked reads without creating dependencies.
Further reading: Signals overview · linkedSignal · Effects
Practice questions
When should you use linkedSignal instead of computed?
Answer
When the value must be derived from a source but also writable by the user, resetting whenever the source changes, such as a selection that resets when the list changes.
Why shouldn't you use effect to sync one signal into another?
Answer
It runs asynchronously, can cause extra change detection cycles and inconsistent intermediate states; computed or linkedSignal express derived state directly.
What does untracked do?
Answer
It reads a signal inside a reactive context without registering it as a dependency, so changes to it don't re-run the computed or effect.
Open in the app to mark lessons complete, take notes and track your progress.