All posts

Angular Signals in Practice: Replacing RxJS Where It Makes Sense

Angular Signals in Angular applications
Angular Signals in Angular applications

Angular's Signals API has matured to the point where it genuinely simplifies many reactive patterns that previously required RxJS. This is not about replacing RxJS entirely — Observables are still the right tool for async event streams, HTTP, and complex multi-source coordination. But for local component state and derived values, Signals are often cleaner and easier to reason about.

The Core Idea

A Signal is a reactive primitive that holds a value and notifies any consumers when that value changes. Unlike an Observable, it always has a current value you can read synchronously. This removes an entire class of loading/undefined boilerplate.

import { signal, computed, effect } from '@angular/core';

const count = signal(0);
const doubled = computed(() => count() * 2);

effect(() => {
    console.log(`count is ${count()}, doubled is ${doubled()}`);
});

count.set(5); // triggers effect automatically

Compare this to the equivalent with BehaviorSubject:

import { BehaviorSubject, combineLatest, map } from 'rxjs';

const count$ = new BehaviorSubject(0);
const doubled$ = count$.pipe(map(n => n * 2));

combineLatest([count$, doubled$]).subscribe(([c, d]) => {
    console.log(`count is ${c}, doubled is ${d}`);
});

count$.next(5);

Both work, but the Signals version has less ceremony for this kind of local state.

Component State Management

The biggest win is in components where you previously used multiple BehaviorSubjects to track related state:

// Before: lots of BehaviorSubjects
@Component({ ... })
export class FilterComponent {
    private query$ = new BehaviorSubject('');
    private category$ = new BehaviorSubject<string | null>(null);
    readonly results$ = combineLatest([this.query$, this.category$]).pipe(
        debounceTime(200),
        switchMap(([query, category]) => this.api.search(query, category))
    );
}

// After: Signals for state, RxJS only for the HTTP call
@Component({ ... })
export class FilterComponent {
    readonly query = signal('');
    readonly category = signal<string | null>(null);
    readonly results = toSignal(
        toObservable(computed(() => ({ query: this.query(), category: this.category() }))).pipe(
            debounceTime(200),
            switchMap(({ query, category }) => this.api.search(query, category))
        )
    );
}

The toSignal and toObservable bridge utilities make it easy to mix both approaches.

When to Keep RxJS

Signals are not a full replacement. Stick with RxJS for:

  • HTTP requestsHttpClient returns Observables; the error handling model is well-understood
  • WebSocket / SSE streams — continuous event sources map naturally to Observables
  • Complex operatorsswitchMap, mergeMap, debounceTime, retry have no Signal equivalents
  • Router eventsRouter.events is an Observable stream

A good rule of thumb: use Signals for state that lives inside a component or service, and use RxJS for the boundaries between your app and the outside world.

OnPush and Signals

One significant benefit worth calling out: components using Signals work perfectly with ChangeDetectionStrategy.OnPush without any extra work. Angular's change detection understands Signals natively and will only re-render when a Signal your template reads actually changes.

This was previously a pain point with OnPush — you had to carefully manage which Observables were subscribed via the async pipe, and forgetting to mark for check in the right places led to subtle bugs. With Signals, Angular handles this tracking automatically.

Practical Migration Path

If you're maintaining an existing Angular app, there's no need to rewrite everything at once. The pragmatic approach:

  1. New components: write with Signals from the start
  2. Existing components: migrate when you're already touching the file for another reason
  3. Services: keep RxJS for anything that involves HTTP or complex stream coordination
  4. Shared state across components: consider a Signal-based store (Angular's built-in store patterns are evolving here)

The Angular team has done a good job making the migration gradual — there's no forced choice between the two systems.

Share

Have thoughts on this? Reach out directly.

Discuss this article
verixon

IT consulting, software development, and digital transformation for companies in Regensburg, Upper Palatinate, Bavaria and DACH-wide.

Get in touch

Want to discuss a project, workflow, or modernization initiative?

Contact verixon

All rights reserved. © 2026 verixon