# Angular Signals in Practice: Replacing RxJS Where It Makes Sense

![Angular Signals in Angular applications](/blog/2026-03-26/2026-03-26-hero_en.svg)

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.

```typescript
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:

```typescript
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:

```typescript
// 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 requests** — `HttpClient` returns Observables; the error handling model is well-understood
- **WebSocket / SSE streams** — continuous event sources map naturally to Observables
- **Complex operators** — `switchMap`, `mergeMap`, `debounceTime`, `retry` have no Signal equivalents
- **Router events** — `Router.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.
