CodiotFree estimate
Web & frontend

Angular signals explained: when RxJS still wins

Abhishek Vankar··7 min read

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 caseSignalsRxJS
Local component stateNatural fitOverkill
Derived / computed valuesNatural fit with computedHeavier than needed
Simple shared stateGood fitWorkable but more machinery
HTTP and async dataNot its jobThe right tool
Event streams, debounce, combine, retryNot its jobThe right tool
Complex time-based coordinationNot its jobThe 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.

FAQ

Do signals replace RxJS?
No, and treating them as a replacement is the most common misread. Signals take over the synchronous, state-holding work that observables were often stretched to cover, such as local component state and derived values, where they are simpler and lighter. RxJS keeps the jobs it was designed for: asynchronous data, event streams, and the operators that debounce, combine, and transform those streams over time. The two are complementary, not competing. A healthy Angular app after signals arrive usually has fewer observables doing simple state and the remaining observables doing the genuinely async and streaming work they are good at.
Which Angular version do I need for signals?
You need a reasonably recent Angular version, because signals were added as a newer feature and matured over subsequent releases rather than existing from the start. Rather than trust a version number quoted in an article that may be out of date, check the current Angular documentation for the version where signals and their related APIs are stable, since the details have moved as the feature settled. The practical guidance is that if you are on an older major version, adopting signals is one more reason to plan an upgrade, and if you are on a current version, they are available to you now.
Are Angular signals production-ready?
Yes, signals have moved from early preview to a stable, supported part of Angular, so using them in production is a reasonable choice rather than a gamble. As with any maturing feature, the surrounding ecosystem and best practices are still settling, so expect patterns to keep improving and some rough edges at the margins. The sensible posture is to use signals confidently for the state and derived-value work they are clearly good at, while keeping RxJS for async and streams, rather than either avoiding signals as unproven or rewriting everything around them on day one.
Should a new project default to signals?
A new Angular project should reach for signals as the default for component state and derived values, and reach for RxJS when the problem is genuinely asynchronous or stream-based. Starting fresh is the easiest place to get the division of labour right, because there is no existing observable-heavy code to unwind. The mistake to avoid in a new project is the opposite overcorrection: forcing signals onto async and event-stream problems they are not built for, just because they are new. Default to signals for synchronous state, keep RxJS for what it does best, and the codebase stays clean.
Start

Got an idea? Let's build it.

Tell us what you're making. We'll reply within two business days with an honest take on scope, timeline, and cost.

Get a free estimate