Angular Signals: What They Are and When to Use Them
A plain-English explanation of Angular Signals — what they replace, when to use signal vs computed vs effect, and the common mistakes that break reactivity.
If you've touched Angular in the last year, you've heard about Signals. If you haven't touched Angular in a few years, Signals are probably the single biggest reason to come back and look again.
I'll explain what they are, what they replace, and where they actually matter — without the framework-engineer jargon.
The short version
A signal is a value that knows when it changes. You read it like a function — count() — and write to it with count.set(5). When the value changes, anything that depends on it updates automatically. That's the whole idea.
```typescript const count = signal(0); const doubled = computed(() => count() * 2);
count.set(5); // doubled() is now 10 ```
If you've used Vue refs or Solid signals, this is familiar. If you've only seen old Angular, this looks nothing like RxJS — and that's the point.
What signals replace
Two things, mostly:
- Zone.js change detection. Angular used to detect changes by patching the entire JavaScript runtime — every
setTimeout, every promise, every event. Signals let Angular skip that and only re-render the parts that depend on changed data. It's faster and a lot less magical. - A lot of RxJS for simple state. RxJS is powerful, but it was overkill for "I have a counter." Signals are the right size for component-level state.
You can still use RxJS where it shines — streams of events, complex async flows, debouncing — and Angular gives you toSignal() to convert observables to signals at the boundary.
When to use a signal
Use a signal when you have a value the UI cares about. A form field, a toggle, a counter, a list of items. Most component state.
```typescript @Component({...}) export class TodoList { protected readonly todos = signal<Todo[]>([]); protected readonly filter = signal<'all' | 'active' | 'done'>('all');
protected readonly visible = computed(() => { const f = this.filter(); return this.todos().filter(t => f === 'all' ? true : f === 'active' ? !t.done : t.done ); }); } ```
The template reads visible() and Angular figures out when to re-render. No subscriptions to manage, no unsubscribe on destroy.
The three building blocks
signal(initial) — a writable signal. Read with (), write with .set() or .update().
computed(() => ...) — a derived signal that depends on other signals. Recomputes only when its inputs change.
effect(() => ...) — runs side effects when signals change. Use sparingly. Most of the time you want computed, not effect. Effects are for syncing with things outside Angular — logging, localStorage, a third-party library.
If you find yourself reaching for effect to update one signal from another, use computed instead.
What changed in templates
The new control flow syntax (@if, @for, @switch) works hand-in-hand with signals. It tracks what each block depends on and only re-runs the parts that need to update.
```html @if (user(); as u) { <p>Hello, {{ u.name }}</p> }
@for (item of visible(); track item.id) { <li>{{ item.title }}</li> } ```
This is what zoneless Angular looks like — no ngIf, no ngFor, no implicit change detection running everywhere.
The mistakes I see
- Caching signal values into plain variables. Don't. You lose the reactivity. Read the signal where you need it; Angular dedups calls.
- Using
effectas glue. If you're updating signal B whenever signal A changes, B should be acomputedof A. - Mutating signal values in place.
todos().push(...)doesn't trigger an update. Use.update(arr => [...arr, newItem]). - Putting heavy work in
computed. Computeds run synchronously when read. Keep them cheap; move expensive work to a service or worker.
The verdict
Signals are the rare framework change that makes the framework simpler and more powerful at the same time. If you're starting a new Angular project today, build it on signals from day one. If you have an older Angular app, you can migrate piece by piece — they coexist with the old reactivity model fine.
For the rest of the modern Angular setup, see Standalone Components.
If you're standing up a new Angular app and want a sanity check on the architecture before you commit, send me a message. I've shipped enough of them to spot the wrong turns early.
Want help shipping this on your own site?
Free 30-minute consultation. No pressure either way.
