Angular Signals vs RxJS Observables: A Practical Comparison

by Didin J. on Aug 07, 2025 Angular Signals vs RxJS Observables: A Practical Comparison

Compare Angular Signals vs RxJS Observables with real examples. Learn their differences, best use cases, and how to use both in your Angular apps.

Angular has long relied on RxJS Observables as the backbone of its reactive programming model. From HTTP requests to component interactions and state management, Observables have been the go-to solution. However, with the introduction of Signals in Angular 16+, a new way to manage reactivity is now available—one that emphasizes simplicity, predictability, and automatic dependency tracking. In this tutorial, we’ll walk through a practical, side-by-side comparison of Angular Signals and RxJS Observables, highlighting their core differences, strengths, and ideal use cases. Whether you’re building a new app or maintaining an existing one, understanding when to use Signals or stick with Observables is essential for writing efficient, maintainable Angular code.


1. Understanding the Basics

What Are RxJS Observables?

RxJS Observables are a powerful and flexible way to handle asynchronous and event-based data in Angular. At their core, Observables represent a stream of values over time—allowing developers to subscribe to changes and apply transformations using a rich set of operators like map, filter, switchMap, and more.

In Angular, Observables are most commonly used for:

  • Making HTTP requests with HttpClient

  • Handling user input and events

  • Communicating between components

  • Managing application state in a reactive way

Here’s a simple example of an Observable in action:

import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-users',
  template: `<ul><li *ngFor="let user of users">{{ user.name }}</li></ul>`,
})
export class UsersComponent implements OnInit {
  users: any[] = [];

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get<any[]>('https://jsonplaceholder.typicode.com/users')
      .subscribe(data => this.users = data);
  }
}

This example uses the HttpClient service, which returns an Observable. The .subscribe() method is used to receive the emitted data and update the component state.

What Are Angular Signals?

Signals are a new reactivity primitive introduced in Angular 16+ as part of Angular’s push toward fine-grained reactivity. Unlike Observables, which are push-based, Signals are pull-based—meaning they only update when explicitly accessed, and Angular automatically tracks dependencies.

Key concepts in Signals:

  • signal(): holds a reactive value

  • computed(): derives new values from signals

  • effect(): performs side effects when signals change

Here’s the same example using Signals instead of Observables:

import { Component, computed, effect, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-users',
  template: `<ul><li *ngFor="let user of users()">{{ user.name }}</li></ul>`,
})
export class UsersComponent {
  users = signal<any[]>([]);

  constructor(http: HttpClient) {
    const users$ = http.get<any[]>('https://jsonplaceholder.typicode.com/users');
    const usersSignal = toSignal(users$, { initialValue: [] });

    effect(() => {
      this.users.set(usersSignal());
    });
  }
}

This version uses signal() to store the data and effect() to update the signal when the HTTP Observable emits. The toSignal() function bridges Observables with the new Signals API, allowing gradual migration or hybrid use.


2. Core Differences Between Signals and Observables

As Angular continues evolving its reactivity model, it's important to understand the conceptual and practical differences between Signals and RxJS Observables. While they both enable reactive programming, they are fundamentally different in behavior, syntax, and use cases.

Push-Based vs Pull-Based

  • RxJS Observables are push-based: They emit values over time, and subscribers are notified whenever new data is pushed.

  • Signals are pull-based: They only update consumers when accessed. Angular tracks which computations or effects depend on them and automatically updates those parts when the signal’s value changes.

Concept Observables Signals
Data Flow Push (emit values) Pull (access values)
Execution Lazy, starts on subscription Eager, reactive on access

Asynchronous vs Synchronous

  • Observables naturally handle asynchronous operations like timers, HTTP calls, or user events.

  • Signals are primarily designed for synchronous state updates, but can be connected to async streams via toSignal().

Subscriptions vs Automatic Tracking

  • With Observables, you must manually subscribe and unsubscribe (or use async pipe in templates).

  • Signals require no manual subscription. Angular handles dependency tracking internally using effect() and computed().

Example:

const count = signal(0);
const doubleCount = computed(() => count() * 2); // Automatic tracking

Learning Curve and Developer Experience

  • Observables have a steep learning curve, especially when chaining multiple operators (switchMap, mergeMap, etc.).

  • Signals have a simpler mental model and are easier for beginners to adopt, especially for local component state.

Interoperability

  • Signals and Observables can coexist. You can convert between them using:

    • toSignal(observable) from @angular/core/rxjs-interop

    • fromObservable() (upcoming utility for hybrid use cases)

These differences make each tool suitable for different scenarios. In the next section, we’ll walk through side-by-side practical examples to demonstrate how to use both effectively.


3. Practical Comparison Examples

Let’s explore how Angular Signals and RxJS Observables differ in real use cases. We’ll go through examples for state management, derived values, side effects, and HTTP calls—each implemented using both approaches side by side.

Example 1: Basic State Management (Counter)

Using RxJS BehaviorSubject

// counter.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class CounterService {
  private countSubject = new BehaviorSubject<number>(0);
  count$ = this.countSubject.asObservable();

  increment() {
    this.countSubject.next(this.countSubject.value + 1);
  }
}
// counter.component.ts
import { Component } from '@angular/core';
import { CounterService } from './counter.service';

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ count$ | async }}</p>
    <button (click)="increment()">+</button>
  `,
})
export class CounterComponent {
  count$ = this.counterService.count$;

  constructor(private counterService: CounterService) {}

  increment() {
    this.counterService.increment();
  }
}

Using Signals

// counter.service.ts
import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CounterService {
  count = signal(0);

  increment() {
    this.count.set(this.count() + 1);
  }
}
// counter.component.ts
import { Component } from '@angular/core';
import { CounterService } from './counter.service';

@Component({
  selector: 'app-counter',
  template: `
    <p>Count: {{ counterService.count() }}</p>
    <button (click)="counterService.increment()">+</button>
  `,
})
export class CounterComponent {
  constructor(public counterService: CounterService) {}
}

Takeaway: Signals offer simpler syntax and no need for .subscribe() or pipes for the local state.

Example 2: Derived Values (Double Counter)

With RxJS (map operator)

import { map } from 'rxjs/operators';

doubleCount$ = this.counterService.count$.pipe(
  map(count => count * 2)
);
<p>Double Count: {{ doubleCount$ | async }}</p>

With Signals (computed)

doubleCount = computed(() => this.counterService.count() * 2);

Signals: effect

effect(() => {
  console.log('New count:', this.counterService.count());
});

Takeaway: effect() automatically tracks dependencies and cleans up on component destruction.

Example 4: HTTP Requests

With Observables (traditional)

this.http.get<User[]>('/api/users')
  .subscribe(users => this.users = users);

With Signals (toSignal)

users = toSignal(this.http.get<User[]>('/api/users'), {
  initialValue: [],
});

Takeaway: Signals can wrap Observables, but the actual HTTP operation still uses RxJS under the hood.


4. When to Use Signals vs RxJS

Now that you've seen practical examples, the big question becomes: When should you use Signals, and when should you stick with Observables? The answer depends on your application's needs and the type of reactivity you're dealing with.

When to Use Signals

Angular Signals are ideal for:

  • Local component state (like counters, toggles, form values)

  • Derived state where you want simple, synchronous computation using computed()

  • Automatic reactivity without managing subscriptions

  • Side effects that should re-run when a value changes using effect()

  • Cleaner templates, especially when displaying reactive values without async pipe

Example Use Cases:

  • UI toggles (isDarkMode, isMenuOpen)

  • State for small form inputs

  • Computed display values (fullName = computed(() => first() + last()))

When to Use RxJS Observables

RxJS Observables remain powerful and necessary for:

  • Asynchronous operations (HTTP requests, timers, intervals, WebSocket streams)

  • Complex reactive flows (like switchMap, debounceTime, etc.)

  • Event streams (user input, mouse clicks, custom events)

  • Global state management using libraries like NgRx, Akita, etc.

  • Stream composition, retry logic, and cancelation behaviors

Example Use Cases:

  • HTTP requests with error handling

  • Live search (with debounce, switchMap)

  • Real-time updates via WebSocket

  • Reactive form validations and changes

🤝 When to Combine Both

Angular provides utilities like toSignal() and (soon) fromSignal() to bridge the gap between Signals and Observables. In practice, you'll likely end up combining both:

  • Use Observables for asynchronous data streams

  • Use Signals to store and derive UI state from those streams

Example:

// Convert an Observable to a Signal
userSignal = toSignal(this.http.get<User>('/api/user'), {
  initialValue: null,
});

⚠️ Summary Decision Matrix

Use Case Recommended
UI state ✅ Signals
HTTP requests ✅ Observables
Async data pipelines ✅ Observables
Derived/computed state ✅ Signals
Reactive side effects ✅ Signals
Long-lived event streams ✅ Observables
Template reactivity ✅ Signals
Forms and valueChanges ✅ Observables


5. Performance and Developer Experience

Angular’s move toward fine-grained reactivity with Signals is not just about syntax—it also offers potential performance improvements and a more approachable developer experience. Let’s break it down:

Performance

Change Detection Optimization

  • Observables rely on Angular's zone-based change detection, which means any async event (HTTP response, timeout, etc.) can trigger a full component tree check.

  • Signals are designed to work with zone-less applications using Angular’s new Signal-based change detection strategy (currently in developer preview). This enables Angular to re-render only what changed, resulting in faster UIs.

Granular Updates

  • Signals track dependencies more precisely than Observables.

  • When you use computed() or effect(), Angular knows exactly what depends on what, so it only recomputes when necessary.

Example:

const first = signal('Jane');
const last = signal('Doe');
const fullName = computed(() => `${first()} ${last()}`);

// Only recomputes when either `first` or `last` changes

🧑‍💻 Developer Experience

Signals: Clean and Intuitive

  • No need for .subscribe(), .pipe(), or async pipe in templates.

  • Easier to read, debug, and reason about.

  • Eliminates the need to manually manage subscriptions and ngOnDestroy() cleanup.

Observables: Powerful but Verbose

  • Require understanding of reactive streams, operators, and lifecycle management.

  • Offer powerful composition, cancellation, and error handling—but at the cost of complexity.

🧪 Tooling Support

  • Observables have mature tooling in VS Code, Chrome DevTools (Redux DevTools for NgRx), and RxJS debugging tools.

  • Signals are still evolving, but Angular DevTools is adding support for Signal inspection and tracing.

🛠️ Maintainability

  • Signals make a simple state simple. Less boilerplate = fewer bugs.

  • Observables are better for shared state and complex orchestration, but require consistent patterns to avoid memory leaks and hard-to-debug behavior.

Conclusion:

  • For many component-level needs, Signals provide a lighter, faster, and more ergonomic alternative.

  • For heavy async workflows, Observables remain indispensable.


6. Migration and Compatibility Tips

As Angular evolves, many developers wonder: "Can I start using Signals now without breaking my existing Observable-based code?" The good news is yes—Angular provides tools and best practices to help you gradually adopt Signals while keeping your current architecture intact.

🔁 Mixing Signals and Observables

Angular introduced interoperability utilities in the @angular/core/rxjs-interop package to seamlessly convert between Observables and Signals.

Convert Observable to Signal

Use toSignal() to bridge async streams into the Signals world:

import { toSignal } from '@angular/core/rxjs-interop';

const userSignal = toSignal(this.userService.user$, {
  initialValue: null,
});

Convert Signal to Observable (coming soon)

A fromSignal() utility is planned to allow Signals to emit as Observables:

// Future syntax (not yet stable)
const count$ = fromSignal(countSignal);

This will allow seamless interop with RxJS pipes, event systems, and forms.

Coexistence Strategy

Rather than refactoring everything, aim to adopt Signals gradually:

Use Case Migration Tip
Local component state Replace with signal()
Derived values Use computed() instead of map()
Side effects Use effect() instead of subscribe()
Async operations (HTTP, forms) Keep using Observables, convert if needed
Global/shared state Stick with RxJS or state libraries like NgRx

⚠️ Common Pitfalls to Avoid

  1. Directly mutating signals: Always use .set() or .update() methods.

  2. Not using () to read signal values: Remember, you must call signals like a function:

    console.log(count()); // not count
  3. Using signals where streams are required: Don’t overuse Signals for async workflows like polling or socket streams—RxJS is still better suited.

  4. Forgetting cleanup for effects: Effects in services or long-lived contexts should be scoped with lifetimes (coming in future Angular versions).

🧩 Working with Third-Party Libraries

  • Most third-party Angular libraries still rely on RxJS.

  • You can wrap their outputs in toSignal() or keep them as Observables and use the async pipe.

Example:

<!-- Observable remains unchanged -->
<div *ngIf="user$ | async as user">Hello, {{ user.name }}</div>


7. Conclusion

Angular’s new Signals API marks a significant step forward in simplifying and optimizing reactive programming within the Angular ecosystem. While RxJS Observables continue to be indispensable for handling asynchronous operations and complex data streams, Signals shine in the realm of local state management, derived values, and fine-grained UI reactivity.

Here’s a quick recap:

  • Use Signals for: local and derived state, cleaner templates, side effects, and performance-friendly change detection.

  • Use Observables for: async workflows, event streams, HTTP requests, form controls, and advanced stream composition.

  • Use both together where needed: thanks to interop utilities like toSignal(), Angular allows gradual and safe adoption of Signals without rewriting your entire codebase.

Ultimately, Angular Signals and RxJS Observables aren’t rivals—they’re complementary tools. Mastering both will give you the flexibility and power to build modern, reactive, and efficient Angular applications.

You can find the full source code on our GitHub.

=====

If you don’t want to waste your time designing your front-end or your budget to spend by hiring a web designer, then Angular Templates is the best place to go. So, speed up your front-end web development with premium Angular templates. Choose your template for your front-end project here.

That's just the basics. If you need more deep learning about Angular, you can take the following cheap course:

Thanks!