Angular signals explained: when RxJS still wins
Angular signals are a reactivity primitive that holds a value and notifies whatever reads it when that value changes, giving you fine-grained updates without subscriptions or manual change detection. They do not replace RxJS. They take over the synchronous, state-holding work that observables were often overused for, while RxJS keeps the asynchronous, streaming, and event-composition jobs it was built for. The useful question is never signals or RxJS; it is which one for which problem.
What are Angular signals, in one working example
A signal is a value that knows who is reading it, so when the value changes, only the things that depend on it update. That is the whole idea, and a few lines show it more clearly than a paragraph:
const count = signal(0);
const doubled = computed(() => count() * 2);
count.set(1);
// reading count() gives 1; doubled() gives 2;
// any template expression that read either one re-renders, nothing else does.
The signal holds state, computed derives new state from it, and both are read by calling them. When count changes, doubled and any template that used them recompute, and crucially nothing that did not depend on them is touched. That fine-grained tracking is the point: updates follow the actual data dependencies instead of Angular re-checking broad swathes of the component tree. Compared with holding the same value in an observable and subscribing to it, there is no subscription to manage, no async pipe, and no lifecycle to clean up for this synchronous case. The mental model is simple: a signal is a reactive box, computed is a formula over boxes, and reading a box inside a formula or a template wires up the dependency automatically.
Signals vs RxJS: the honest division of labor
Signals and RxJS are not rivals fighting over the same ground; they cover different ground, and the honest division is by whether the problem is synchronous state or asynchronous stream. Signals own the former, RxJS owns the latter, and most of the confusion comes from years of using observables for state simply because that was the reactive tool Angular gave you.
The split is clear enough to tabulate:
| Use case | Signals | RxJS |
|---|---|---|
| Local component state | Natural fit | Overkill |
| Derived / computed values | Natural fit with computed | Heavier than needed |
| Simple shared state | Good fit | Workable but more machinery |
| HTTP and async data | Not its job | The right tool |
| Event streams, debounce, combine, retry | Not its job | The right tool |
| Complex time-based coordination | Not its job | The right tool |
The reading is straightforward: if the value is here now and changes synchronously, a signal is simpler and lighter; if the value arrives over time or is a stream of events you need to transform, RxJS and its operators are what you want. The two also interoperate, so you can bridge an observable into a signal at the boundary where async data becomes local state. The goal is not to purge RxJS; it is to stop using it for synchronous state it was never the best fit for, and to keep it for the async and streaming work where nothing else comes close.
Migrating an existing app: where signals pay off first, and where not to touch
In an existing Angular app, signals pay off first in local component state and derived values, and you should deliberately not touch working async and stream code just because signals are new. Migration is a place to be selective, because the point is to reduce complexity, not to trade one large rewrite for another.
Start where the win is obvious and the risk is low: component-local state currently held in a BehaviorSubject with an async pipe, and values computed from other values through chains of operators that exist only to derive synchronous state. Those convert cleanly to signal and computed, and the code usually gets shorter and easier to read. Leave alone the code that is genuinely doing RxJS's job: HTTP calls, debounced inputs, combined streams, retries, and anything coordinating events over time. Rewriting that into signals is effort spent making working code worse. The honest migration is incremental and value-led: convert the synchronous state that observables were awkwardly holding, keep the streams as streams, and resist the urge to make it a project. A codebase where signals and RxJS each do what they are best at is the destination, and you can get there a component at a time.
The mistakes teams make in month one
The first-month mistakes with signals are almost all the same error in two directions: using signals for async work they are not built for, or refusing to let RxJS go for the state it was never good at. Both come from treating signals as a replacement rather than a complement.
The most common mistake is reaching for signals on asynchronous problems, trying to force HTTP responses or event streams into a model designed for synchronous state, and ending up reinventing worse versions of RxJS operators. The mirror-image mistake is dogmatically keeping everything in observables, adding async pipes and subscriptions to hold values that are simply present and synchronous, because that is the familiar habit. A third is over-deriving: wrapping everything in computed until the dependency graph is hard to follow, when a plain value would do. And a subtle one is forgetting that signals change how and when the view updates, so teams occasionally fight change detection instead of leaning into the fine-grained model. None of these are hard to avoid once the division of labour is clear; they happen when a team adopts signals as a trend rather than as a tool with a specific job.
Do signals change the Angular vs React conversation?
Signals narrow one gap in the Angular versus React conversation, around fine-grained reactivity, but they do not change the fundamentals of that choice, which are about ecosystem, team fit, and architecture rather than a single primitive. It is worth being brief and honest here rather than overclaiming.
Fine-grained, signal-style reactivity is a pattern several frameworks have moved toward, and Angular adopting it means one less axis on which the two frameworks feel categorically different. But the decision between them was never really about reactivity primitives; it is about the whole framework's opinions, the team's experience, the hiring market, and the shape of the application, which is exactly what our Angular vs React for enterprise apps comparison works through. Signals make Angular's reactivity feel more modern and more like its peers; they do not settle the larger choice, and any argument that a single feature does should be treated with suspicion.
How to introduce signals without a rewrite
You introduce signals without a rewrite by adopting them for new code and converting existing state opportunistically, never as a big-bang migration. Signals and RxJS coexisting is the normal, healthy state of an Angular codebase, so there is no finish line to sprint toward.
The practical approach is threefold. Write new components and new state with signals by default, so the codebase moves in the right direction without any migration project. Convert existing synchronous state to signals when you are already touching that code for another reason, which spreads the change across normal work instead of concentrating it into risky churn. And leave the async and streaming code in RxJS unless there is a concrete reason to change it. This is the same measured approach we take to modernising Angular apps generally, which sits alongside the wider upgrade path in our Angular upgrade guide. Introducing signals should make an app simpler in the places it touches and leave the rest working; if adopting them turns into a rewrite, the adoption has lost the plot. For teams doing this at scale, our web development work and dedicated Angular developers can carry the incremental migration, and a conversation is the way to size where signals pay off first in your specific app.