AcademyAngular
Signals: signal(), computed() and effect()
signal() holds a value behind a getter/setter pair; computed() derives from it lazily; effect() reacts to it. Angular's answer to Vue's ref or a React hook.
2 min readUpdated 3 exercises
signal() is called to read, and called with an argument to write
signal(initialValue) returns a function-shaped object: calling it with no arguments reads the current value, and .set()/.update() write a new one:
count = signal(0);
increment(): void {
this.count.update((n) => n + 1);
}The bug shape this invites is subtle: this.count.update((n) => n++) looks like it increments, but n++ returns the old value of n as the expression's result — post-increment evaluates to the pre-increment value — so the updater function returns the unchanged number back into .update(), and repeated clicks visibly do nothing, or produce NaN once that stale value feeds into something else expecting a number. (n) => n + 1 is the version that actually returns the new value.
computed() is lazy and re-derives only from the signals it reads
computed(() => expr) returns a read-only signal that recomputes only when a signal it actually depends on changes — not on a timer, not on every change-detection pass:
count = signal(0);
doubled = computed(() => this.count() * 2);If doubled looks like it "never updates," the usual cause is that the template reads a plain field instead of calling the computed signal — {{ doubled }} instead of {{ doubled() }} — which prints the function reference's string form instead of its result, since a signal is a callable, not a plain value.
effect() runs after creation, not synchronously in the constructor
effect(() => { ... }), registered in a constructor or field initializer, runs once shortly after creation and again whenever a signal it reads changes — but not synchronously the instant you write it, and never before the component's fields have actually been assigned. An effect that mutates a signal it also reads, in place, deadlocks conceptually the same way a Vue watcher mutating its own source does: reassign a whole new array or object from inside the effect, do not .push() onto the one you just read.
What interviewers check
Whether you know that signal() is read by calling it, that computed() is lazy rather than eager, and whether you have hit the "n++ doesn't increment a signal" bug yourself — it is the fastest way to tell someone who has written Signals code from someone reciting the API.
Now practice it
1 quiz · 2 drillsReading this page is the cheap half. These are the exercises that make you use it.
- Signals — signal() and computed()Fix a counter whose increment produces NaN because it returns the pre-increment value, and a "doubled" display stuck on a plain field.DrillSolve · EasyDrillSolve · Easy
- effect()Fix a change log that grows internally but never re-renders, and a reset that wipes the signal instead of clearing its contents.DrillSolve · EasyDrillSolve · Easy
- Fundamentals · Advanced AngularHarder Angular questions at the level where Signals timing and dependency tracking start to matter.QuizTake · HardQuizTake · Hard
Read next
Event Binding and Custom Outputs
(event)="handler($event)" binds a native DOM event; @Output() lets a component emit its own, typed the same way any other value in the app is typed.
2 min
Two-Way Binding and Reactive Forms
[(ngModel)] is sugar over a property and event bound together; reactive forms trade that convenience for FormControl, validators and explicit state.
2 min