Jul 15, 2026

Angular Signals Explained: signal(), computed() & effect()

angularsignalsrxjsfrontendstate

Learn Angular Signals: signal(), computed(), and effect() for fine-grained reactive state. The BehaviorSubject alternative with full RxJS interop.

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 with computed(). When a signal changes, only the parts of the UI that read it re-render — no zone.js, no manual subscriptions, no async pipe.

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:

  1. What fine-grained reactivity is
  2. How signals work
  3. How they differ from RxJS
  4. 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.

signal() vs. computed(): what’s the difference?

signal()computed()
ValueMutable — set directlyDerived, read-only
How you change it.set(value) or .update(fn)Recalculates automatically when its dependencies change
Typical useSource of truth for component stateValues derived from one or more signals

RxJS vs Signals: How Is This Different?

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.

SignalsObservables (RxJS)
NatureSynchronous, always hold a current valueModel values over time — can be async
ReadingCalled directly: people(), no subscriptionRequires subscribe() or the async pipe
LifecycleNo completion, no manual cleanupCan complete or error; needs unsubscribe()
Best forComponent state, derived valuesHTTP responses, WebSocket streams, event pipelines

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.

Written by

Antonio Cárdenas

Google Developer Expert in Angular · Verified GDE profile · Google Developers profile

My technical writing reaches 100,000+ developers in English and Spanish. I've migrated production Angular apps from v8 to v22 — everything I publish is built from real project work, not documentation re-reads.

Frequently asked questions

FAQs

What are Angular Signals?

Angular Signals are a fine-grained reactivity system introduced in Angular 16. A Signal is a value container that automatically notifies its consumers when it changes. You read its value by calling it as a function: mySignal(). You create one with signal(initialValue) and change it with .set() or .update().

Which Angular version introduced Signals?

Signals were introduced as an experimental API in Angular 16 (May 2023) and stabilized as a public API in Angular 17. Since Angular 19, they are the recommended way to manage reactive state in components, together with NgRx SignalStore for global state.

Do Signals replace RxJS in Angular?

Not completely. Signals replace RxJS for local component state and synchronous derived values. RxJS remains ideal for complex async flows, HTTP requests, and event streams. Angular provides toSignal() and toObservable() for interoperability between both systems.

What is the difference between signal() and computed() in Angular?

signal() creates a mutable reactive value that you change with .set(newValue) or .update(fn). computed() creates a read-only derived value that recalculates automatically when its dependencies change. You cannot assign a value directly to a computed — it only updates when its input signals change.

How do I use Signals with existing Observables and async pipes?

Use toSignal(observable$) to convert an Observable into a Signal. It manages subscription and cleanup automatically. For the reverse direction, use toObservable(mySignal) to get an Observable from a Signal. Both utilities live in @angular/core/rxjs-interop.

Keep reading

Building something with Angular?

I write about Angular architecture, Signals and the tooling around them — migration guides, real upgrade paths, and the catches most posts skip.

Browse all articlesAbout me

More from the blog