TL;DR: A Signal is a reactive value container: create it with
signal(0), read it by calling it like a function (count()), change it with.set()or.update(), and derive read-only values withcomputed(). When a signal changes, only the parts of the UI that read it re-render — no zone.js, no manual subscriptions, noasyncpipe.
Reactive programming has a steep learning curve. For a lot of developers starting with Angular, RxJS was the wall they hit first: Observables, subscriptions, operators, teardown. Signals change that. Here’s what we’ll cover:
- What fine-grained reactivity is
- How signals work
- How they differ from RxJS
- How they make Angular easier to use
What is fine-grained reactive state?
Fine-grained reactivity is a programming paradigm where code re-evaluates automatically in response to changes in data or state. The code is split into small, independent reactive units that are sensitive to changes in their inputs. When an input changes, those units re-evaluate automatically and update their output accordingly.
Fine-grained reactivity is the model behind frameworks like Solid.js, Vue.js, and Svelte — and now Angular. The UI updates automatically in response to changes in the underlying data: components are defined as reactive units that re-evaluate when the application state they depend on changes.
The payoff is performance and maintainability. There’s less need for manual UI updates, data processing is more efficient, and the UI stays consistent with the underlying data at all times.
How signals work
Imagine a variable that holds a value, like any other — the interesting part is that when the signal changes, everything that reads that signal updates too. This saves time and resources, because Angular no longer has to check the whole component tree to figure out what changed.
Classic Angular:
@Component({
template: `
{{ people }}
<button (click)="addPeople()">Add people</button>
`,
})
export class AppComponent {
people = 0;
addPeople() {
this.people = 5;
}
}Using Signals:
import { signal, computed } from '@angular/core';
export class AppComponent {
people = signal(0);
doublePeople = computed(() => this.people() * 2);
addPeople() {
this.people.update((count) => count + 1);
}
}Using RxJS:
people$ = new BehaviorSubject(0);
doublePeople$ = this.people$.pipe(map((people) => people * 2));Notice the difference: the computed() version reads like a plain function, needs no subscription, and never leaks. The RxJS version needs a BehaviorSubject, a pipe, and either an async pipe or a manual subscription in the template.
How is this different from RxJS?
The main difference is that RxJS is a specific library for reactive programming with streams, while fine-grained reactivity is a more general programming paradigm that can be implemented with different techniques — signals being Angular’s implementation.
In practice:
- Signals are synchronous, always have a current value, and are read by calling them:
people(). Perfect for component state and derived values. - Observables model values over time — HTTP responses, WebSocket streams, user events. They can be async, can complete, and can error.
You don’t have to choose. toSignal() and toObservable() from @angular/core/rxjs-interop convert between the two, so HTTP calls can stay in RxJS while your template state lives in signals.
How signals make Angular easier
With signals, DOM updates are handled faster and more precisely. Signals are not Observables: you can read their value directly, synchronously, without subscribing — no subscribe(), no unsubscribe(), no memory-leak checklist.
Since Angular 19, signals are the recommended default for component state, and they’re the foundation the framework is building on: zoneless change detection, input() signals, and Signal Forms all sit on top of this reactivity model.
Live examples:
Signals pair naturally with the new template syntax — see Angular Control Flow: @if, @for and @switch Explained for the other half of declarative Angular.
