Angular Signals vs Observables: A Deep Dive into Modern Reactivity
Raju Dandigam

Raju Dandigam @raju_dandigam

About: Engineering Manager with over 13 years of experience transitioning from a front-end engineer to a leadership role. Expertise in developing innovative front-end solutions, leading full-stack teams, and

Location:
Palo Alto, CA
Joined:
Jul 3, 2024

Angular Signals vs Observables: A Deep Dive into Modern Reactivity

Publish Date: Apr 6
15 0

With the release of Angular 16, the framework introduced a powerful new reactive primitive: signals. Signals provide a new way to manage reactivity in your applications, enabling more predictable and efficient UI updates. But how do they compare with the well-established observables? When should you use one over the other? And how do features like computed() come into play?

In this post, we’ll explore Angular signals in depth, compare them with observables, and provide practical use cases to guide your decision-making.

🚦 What Are Angular Signals?

Signals are reactive values that notify dependents when their value changes. They are designed to be synchronous, fine-grained, and easy to use.

A signal is created using the signal() function and acts as a state container:

import { signal } from '@angular/core';

const count = signal(0);

console.log(count());   // Reading value: 0
count.set(5);           // Setting value
count.update(c => c + 1); // Updating value: 6
Enter fullscreen mode Exit fullscreen mode

Key Characteristics:

  • Synchronous: Reading a signal gives you its value immediately.
  • Dependency Tracking: When used inside a computed signal or an effect, it registers itself as a dependency.
  • Reactive: Automatically propagates changes to anything that depends on it.

🧮 Using computed() with Signals

One of the most powerful aspects of signals is the computed() function. It allows you to create derived signals, similar to computed properties in other reactive frameworks like Vue.js.

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

const price = signal(100);
const quantity = signal(2);

const total = computed(() => price() * quantity());

console.log(total()); // Output: 200

quantity.set(3);
console.log(total()); // Output: 300
Enter fullscreen mode Exit fullscreen mode

The computed signal automatically recalculates when any of its dependencies change. No manual subscriptions are needed.

🔁 Signals vs Observables

Let’s compare Angular signals with RxJS observables across different dimensions:
Image description

📌 When to Use Signals

✅ Use Signals When:

  • Managing local UI state (e.g. toggle, counters, forms)
  • Creating computed values that derive from other signals
  • You need fine-grained change detection (only dependent parts re-render)
  • You want simpler and more declarative reactivity

Example: Component-local state

@Component({
  selector: 'app-counter',
  template: `
    <button (click)="increment()">Add</button>
    <p>Count: {{ count() }}</p>
  `
})
export class CounterComponent {
  count = signal(0);

  increment() {
    this.count.update(c => c + 1);
  }
}
Enter fullscreen mode Exit fullscreen mode

🚫 Avoid Signals When:

  • You’re dealing with asynchronous data (e.g. HTTP requests, WebSocket streams)
  • You need to compose multiple async streams using RxJS operators (mergeMap, switchMap, etc.)
  • You’re integrating with libraries heavily based on RxJS

🌐 When to Use Observables

✅ Use Observables When:

  • Working with asynchronous streams (e.g. user inputs, API responses)
  • Using RxJS operators to transform streams
  • Handling event-based programming (e.g. WebSockets, form events)
  • Managing global app state with side effects (e.g. NgRx)

Example: Async HTTP data

@Component({
  selector: 'app-data',
  template: `
    <ng-container *ngIf="data$ | async as data">
      <p>{{ data.title }}</p>
    </ng-container>
  `
})
export class DataComponent {
  data$ = this.http.get('/api/data');

  constructor(private http: HttpClient) {}
}
Enter fullscreen mode Exit fullscreen mode

🔄 Interoperability: Signals and Observables

Angular provides utility functions to convert between signals and observables:

toSignal() – Observable to Signal

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

const signalFromObservable = toSignal(myObservable$, { initialValue: null });
Enter fullscreen mode Exit fullscreen mode

toObservable() – Signal to Observable

import { toObservable } from '@angular/core';

const observableFromSignal = toObservable(mySignal);
Enter fullscreen mode Exit fullscreen mode

This enables seamless integration of both paradigms.

💡 Use Case Summary

Image description

🧠 Final Thoughts

Signals represent a shift toward synchronous, declarative state management in Angular. While Observables will always have a place—especially for async operations—signals make state management in UI components much simpler and more intuitive.

Use signals for local, synchronous UI state and computed values. Use observables for asynchronous workflows and complex stream operations. And when needed, bridge the two with Angular’s interop utilities.

The future of Angular is signal-first but observables aren’t going anywhere. Choosing the right tool for the job is key.

Comments 0 total

    Add comment