BairesDev

Angular Signals: Setting a Reactivity Standard Without a Migration

Learn how to standardize Angular Signals for UI state while keeping RxJS where it belongs, no migration required.

Last updated:
13 min read
Verified Top Talent

By Rafael Affonso

Software Engineer8 Years of Experience

Rafael is a full-stack software engineer with 8+ years of experience building scalable web applications. He specializes in building scalable and practical web apps for fintech and healthcare, with deep expertise in optimizing cloud infrastructure on AWS for performance and cost-efficiency.

Angular logo on a purple gradient background with abstract graphics representing reactive state management, data flow, and Angular Signals.

Most Angular codebases in production built their state management on RxJS Observables, which made sense when Observables were the only reactive primitive on offer. Angular Signals changed that equation across versions 16 through 22. New Angular projects now ship zoneless with OnPush components by default, which turns Signals from a feature evaluation into a standards decision.

The open question for engineering leaders is what to standardize for new code, what to leave alone and how to govern the boundary between the two models. The clearest path forward is a documented default: Signals for application and UI state and RxJS for stream composition. It costs almost nothing to adopt, shrinks what new hires have to learn and makes every remaining use of RxJS a deliberate choice.

Key points

  • Make Signals the default for new stateful UI code and leave working Observable code in place.
  • Use RxJS where the problem is a sequence over time and operators earn their place. Event streams, debouncing, cancellation, retry and multi-source coordination have no Signal equivalent.
  • Zoneless readiness is its own modernization project with its own testing burden and timeline.
  • Your team can adopt NgRx SignalStore for new state slices without dismantling the existing Redux store.

When to Adopt Angular Signals: The Short Answer

Use Signals as the default for any new component or service state, including derived display values. Keep RxJS wherever the problem is a sequence over time: real-time feeds, event composition and HTTP workflows that retry, cancel or coordinate multiple requests. Do not fund a migration of working Observable code to reach that standard, because Angular’s interop bridges let both models coexist indefinitely.

What Angular Signals Are and Why They Exist

Angular Signals solve a problem the framework created for itself: because RxJS was the only reactive primitive on offer, every state management task inherited Observable complexity no matter how simple.

Angular Signals are a reactive primitive for managing application and UI state. Introduced in Angular 16 as a developer preview and stabilized across v17 through v22, they let components track state changes without Observables or manual subscriptions and they tell Angular precisely which template consumers depend on a value. The significance for engineering leaders is architectural: a low-complexity default for the state management tasks that never required Observable machinery.

Every signal is writable at its source and read-only wherever it’s exposed. A service can hold the writable signal internally while returning only a read-only view to callers, so external code can read the current value but never set it directly. That split, writable internally and read-only externally, keeps every state change traceable to one place.

The Angular team’s Signals RFC makes the motivation explicit. Zone.js, Angular’s original change detection mechanism, works by monkey-patching browser APIs. Angular learns that something asynchronous happened, but not what changed or who depended on it.

Signals close that gap: when a signal changes, Angular knows exactly which views to mark because reading it inside a template or other reactive context registers a dependency, instead of re-checking the entire component tree the way Zone.js does. Removing Zone.js is a separate decision, and in a zoneless application Signals are one of several mechanisms that schedule change detection, alongside async pipe emissions, template event bindings and input changes.

Diagram comparing Angular Signals and RxJS Observables, highlighting dependency tracking, computed state, template updates, stream operators, cancellation, retry handling, and interoperability through toSignal() and toObservable().

The version arc matters for teams evaluating upgrade timelines. Angular 16 (May 2023) shipped Signals in developer preview. Angular 17 (November 2023) graduated signal() and computed() to stable, and Angular 19 (November 2024) did the same for signal-based input(), output(), model() and view queries.

Angular 20 (May 2025) completed the set, stabilizing effect(), linkedSignal() and toSignal(). Angular 21 (November 2025) made zoneless change detection the default for new projects. Angular 22 (June 2026, the current release) stabilized Signal Forms and the Resource APIs, and made OnPush the default change detection strategy for new components.

The Complexity Gap Signals Were Built to Close

RxJS is a general-purpose stream library, and that generality is the problem when most of what a component needs is to hold a value and know who reads it. The operator catalog runs to over 100 operators; Angular’s Signals API exposes roughly nine core primitives.

That gap is the organizational cost leaders are actually managing. Every developer joining an Observable-heavy codebase builds fluency across the operator library before contributing to state management, and subscription lifecycle adds its own risk. Unmanaged subscriptions cause memory leaks, and failing chains can leak silently too.

This is not a verdict on past decisions. Until Signals existed, Observables were the only primitive on offer. 

Signals close the gap for problems that never required that complexity. A signal is, in Angular’s own definition, “a wrapper around a value” that “notifies interested consumers when that value changes.” In practice, that means a signal behaves like a getter function: call it to read the signal’s current value, the same way you’d call any other function, rather than reading a plain value off an object directly. 

The model is hybrid push/pull: notifications propagate eagerly, but computed values are marked dirty and recomputed only when something reads them, while Observables push both notification and value as the producer emits. What matters architecturally is what each model tells the framework. A computed signal caches its result, the cached value is returned on every read until a dependency changes, so the derivation function only re-runs when it has to, and a custom equality function can further control when a changed value is considered meaningfully different.

A Signal reports a current value and who depends on it. An Observable reports a sequence Angular sees only where the application connects it to a view. Signals also guarantee what Sub-RFC 1 calls “glitch-free consistency”: no intermediate inconsistent state reaches the UI during a computation cycle. Angular treats every signal read inside a computed or effect as a fresh dependency on that source signal within that reactive context; write to it later, and only that computation re-runs.

The Signals RFC makes clear that Signals are not a replacement for RxJS. The Angular team chose not to build reactivity on Observables and is investing in interoperability between the two models. Signals carry application and UI state; RxJS carries temporal composition, event streams, and workflows that need cancellation or retry.

That boundary holds even over the network. A remote response becomes application state the moment it lands, which is why Angular now offers signal-native ways to hold it.

Angular Signals vs. RxJS Observables: A Decision Matrix

The comparison that matters is which model matches the complexity of each problem. Both have a territory where they win clearly.

Table: Angular Signals vs. RxJS Observables Decision Matrix

Dimension Angular Signals RxJS Observables
State Management Scope Application and UI state natively, including derived state; Observable sources arrive via toSignal or the Resource APIs Sequences of values over time; holding state requires additional abstractions (BehaviorSubject, combineLatest)
Reactivity Model Hybrid push/pull: notification propagates eagerly, computed values recompute lazily on read Push: both notification and value are delivered to subscribers as the producer emits
Complexity Baseline Roughly nine core APIs (signal, computed, effect, linkedSignal, resource, and related); significantly lower onboarding overhead Over 100 operators across nine categories; production fluency requires significant investment
Change Detection Integration Every signal read in a template registers a dependency, so Angular marks only the views that consume the value Fully supported in zoneless Angular (default since v21) through Angular-aware consumers; async pipe emissions mark the host view
Async Operation Handling Request-shaped async via the Resource APIs (resource(), httpResource(), rxResource(), stable v22); no operator model for stream composition Native across the full range: HTTP, WebSocket, event debouncing, multi-source composition, retry and cancellation
Performance Angular checks only the views that read signals carrying a changed value; the larger measured gains come from removing Zone.js, which is a separate decision No inherent penalty in zoneless applications when consumed through Angular-aware mechanisms; cost tracks how much work a chain performs per emission
Scalability Codebase: a small API surface keeps state patterns consistent as headcount grows. Runtime: fewer views checked per update as component count rises Runtime: strong for high-frequency sources and backpressure. Codebase: depends on team-wide discipline about which operator subset is standard
Learning Curve Accessible without a reactive programming background; API surface is small and self-contained Requires fluency with functional reactive programming concepts and operator composition
Ideal Use Case Component state, shared state, derived display values, read-only HTTP via httpResource HTTP pipelines with retry/cancellation, real-time streams and complex event composition

The table draws a boundary. The next two sections say what falls on each side of it.

Where Signals Are the Right Default

Signals earn the default because most of what Angular components manage is application state that never needed Observable complexity.

Component-local state

This is the clearest case: toggle visibility, form step tracking, loading indicators, error flags and any conditional logic that only needs the component’s own state. The component tracks each value directly, with no subscription to manage and no teardown to remember.

Shared State and Component Communication

Shared state is the same story. Signal-based services and signal inputs (stable since Angular 19) handle state flowing down to child components and across siblings, where a BehaviorSubject service plus async pipes carries boilerplate disproportionate to the problem.

Derived Display Values and Computed Signals

Derived display values map directly to a computed signal: filtered lists, formatted strings, aggregated totals and values built on top of more complex data structures.

Existing NgRx Patterns

Existing NgRx patterns have a signal-native path. NgRx SignalStore, stable since NgRx v18 (July 2024), is built entirely on Signals, so teams with a mature Redux investment can take new state slices without unwinding the old architecture.

Signals close the most risk where RxJS depth is shallow. Codebases maintained by developers without deep RxJS experience carry subscription leaks, unhandled chain errors and operator misuse, categories that mostly disappear for the tasks Signals cover.

Where RxJS Stays the Right Tool

The boundary of Signals is stream composition. Angular’s own architecture confirms this by keeping HttpClient Observable-based while scoping httpResource to read-only fetching for display.

HTTP Workflows With Retry and Cancellation

HTTP workflows with real branching belong in Observables. HttpClient returns Observables deliberately: retry, cancellation, error recovery and multi-request composition all rely on operators with no Signal equivalent. The httpResource function read-only fetching, and Angular’s documentation advises against using it for mutations.

Real-Time Data Streams

Real-time data streams are sequences, not values. WebSocket connections, server-sent event feeds and polling loops produce an ordered series where each emission matters. Observables model that natively; a Signal holds only the latest value, and only signals that carry that latest emission, so wrapping a socket in one adds a conversion layer and gains nothing.

Event Composition

Event composition is RxJS’s core strength. Debounced search inputs, throttled scroll handlers, merged event sources and races between competing async operations. Operators like combineLatest, switchMap, and debounceTime exist because these problems are hard to solve without a stream abstraction.

Team competency is the last boundary, and it cuts against migration. Where developers are productive in RxJS and the codebase is well maintained, there is no organizational argument for a rewrite. The performance benefits of zoneless arrive without one.

What the Standard Looks Like in Two System Shapes

The boundary is easier to evaluate against whole applications than isolated patterns. The two architectures below are composites, drawn from the system shapes where the Signals/RxJS split matters most. Both run a single architecture with one deliberate seam.

A customer service platform. Agent consoles are dense with state: active filters, the selected record, role-based permissions, computed queue totals. That is all application state read by templates, so it belongs in Signals. The permission model is the clearest example, because a computed signal collapses “what this agent can see” into one derived value, read by every template that needs it. 

The escalation workflow is the exception: creating a case, attaching history, notifying a supervisor and rolling back cleanly on failure is a multi-request sequence with cancellation, and it stays in RxJS.

An operations monitoring dashboard. Ingestion is unambiguously RxJS, a socket feed delivering thousands of events a minute, buffered and throttled before anything reaches the UI. Everything downstream is view state: time window, service filter, threshold configuration and computed rollups. The seam sits at one place, where toSignal() converts the throttled stream into the value the template reads.

Neither shape requires a migration. In both, the RxJS that remains does work Signals cannot do.

Decision flowchart for Angular teams choosing between Signals and RxJS Observables based on HTTP requests, real-time streams, event composition, application state, and NgRx SignalStore.

Setting the Standard Through Governance

The standard works because Angular’s interop infrastructure is stable. The toSignal() function an Observable into a Signal, subscribing immediately and auto-unsubscribing on destroy; toObservable() and rxResource() cover the other direction. The two models coexist at component, service, and module level, so a team can adopt this posture without touching working Observable code.

Zoneless change detection is the larger shift behind this decision. Experimental in Angular 18, developer preview in v20, stable in v20.2, and the default for new projects in v21, zoneless removes Zone.js’s monkey-patching overhead entirely.

Angular 22 completed the shift by making OnPush components the default, preserving the old behavior under the renamed ChangeDetectionStrategy.Eager. Zone.js remains supported, and teams can keep using it. 

The direction is clear enough that zoneless planning belongs on the roadmap. Signal Forms, also stable in v22, confirm the signal surface is still expanding.

Table: Governance Standards for Angular Reactivity

Scenario Recommended Standard Rationale
New component state (local or shared) Use Signals Lower complexity; no subscription management; aligns with the zoneless and OnPush defaults
Derived display values (filtered lists, formatted strings, aggregated totals) Use computed() signals Automatic dependency tracking; no operator chains to maintain
Read-only HTTP fetching for display Use httpResource (stable v22) or toSignal() wrapping HttpClient Signal-native path for request-shaped data; reduces Observable boilerplate for simple reads
HTTP with retry, cancellation, or composition Use RxJS Observable via HttpClient httpResource explicitly advises against mutation; complex HTTP patterns remain Observable territory
WebSocket / SSE / real-time streams Use RxJS Observable Inherently push-based; Observables model push semantics natively
Complex event composition (debounce, merge, race) Use RxJS operators No Signal equivalents for combineLatest, switchMap, debounceTime
Existing NgRx store Use NgRx SignalStore (v18+) for new state; keep existing patterns in place Incremental adoption without abandoning the Redux pattern

Adopting this standard is a documentation exercise, not an engineering project. Three artifacts cover it:

  • A one-page ADR naming Signals as the default for new stateful UI code and RxJS as the default for stream composition, with the table above as its appendix.
  • One question added to code review: is this a value the template reads, or a sequence over time? The first answer means Signals and the second means RxJS.
  • A backlog seeded for free. The v22 migration marks every component that kept the old strategy as Eager. That list is your dependency-awareness cleanup queue, already written.

Code reviews get faster when a reviewer can check a new component against a published standard rather than relitigating patterns case by case. For organizations building or modernizing Angular applications, we provide Angular development services that help teams set and implement these architecture standards.

One scheduling constraint makes this concrete. Angular moved from a six-month to an annual major cycle at v22, each release now carrying 24 months of support: 12 active and 12 LTS. v21 entered LTS the day v22 shipped, and v20 will leave LTS in November 2026. 

Upgrade planning is a calendar obligation. Setting the standard now costs a page of documentation; setting it during an upgrade crunch costs a sprint.

Frequently Asked Questions

  • What Are Angular Signals?

    Angular Signals are a reactive primitive for managing application and UI state. They let components track state changes without Observables or manual subscriptions, and they tell Angular which views depend on each value.

    Angular shipped them as a developer preview in v16 and stabilized the primitives across v17 through v22.

  • Do Angular Signals Replace RxJS?

    No. Signals hold application and UI state. RxJS remains the idiomatic Angular tool for temporal composition: event streams, debouncing and throttling, multi-source coordination and HTTP workflows that need retry or cancellation.

    The Angular team’s Signals RFC treats the two as serving different purposes, investing in interoperability between them. The Resource APIs stable in v22 bring request-shaped async into signal territory, but stream composition stays in RxJS.

  • Should We Migrate Existing RxJS Code to Signals?

    No, not as a standalone project. Angular’s interop bridges let both models coexist indefinitely, so rewriting working Observable code buys tidiness at the cost of regression risk. Apply the standard to new code and let the ratio shift naturally as the codebase turns over.

  • Do Angular Signals Require Zoneless Change Detection?

    No. Signals work fine under Zone.js, and zoneless applications support Observables through async pipe and other Angular-aware consumers. These are separate decisions on separate timelines. Zoneless carries its own testing burden, particularly around code that mutates state outside Angular’s knowledge, so don’t let your Signals adoption block or rush that evaluation.

  • How Do Signals Affect an Existing NgRx Architecture?

    NgRx SignalStore has been stable since NgRx v18 and is built entirely on Signals. Your team can adopt it for new state slices without unwinding the existing store. It’s an incremental path, not a replacement, so the Redux architecture your team already knows stays in place.

  • What Should Change in Code Review?

    Reviewers should ask one question of every new reactive decision: is this a value the template reads, or a sequence over time? The first answer means Signals and the second means RxJS.

  • How Do Angular Signals Affect Performance?

    Signals give Angular dependency-level information, so it marks only the views that read a changed value. Zone.js could only report that an async event had occurred. Most of the measured gain comes from removing it, not from the Signal API.

    Angular’s documentation reports that going zoneless improves payload size, startup time, and Core Web Vitals, without publishing percentages.

  • What Is a Computed Signal?

    A computed signal derives its value from other signals. The computed() function returns a read-only signal that recalculates lazily, only when something reads it and a dependency has changed.

    Use it for filtered lists, formatted strings and aggregated totals, anywhere a value is a function of other state rather than state itself.

  • Do Signals Require OnPush Components?

    No, but they pair well with OnPush components: signals are what make OnPush efficient. They tell the framework exactly which views depend on a changed value.

    Existing components marked Eager by the v22 migration keep working under either model.

By Rafael Affonso

Software Engineer8 Years of Experience

Rafael is a full-stack software engineer with 8+ years of experience building scalable web applications. He specializes in building scalable and practical web apps for fintech and healthcare, with deep expertise in optimizing cloud infrastructure on AWS for performance and cost-efficiency.