← Back to Articles         
ngular Signals vs RxJS Observables

ngular Signals vs RxJS Observables

Published on 01 Oct 2026     10 min read Angular
Angular Signals

Angular Signals vs RxJS Observables

The simplest distinction is:

Signals are primarily for reactive state. Observables are primarily for asynchronous streams/events over time.

They are complementary, not competitors where one completely replaces the other.

Signals
   ↓
"What is the current value/state?"


Observables
   ↓
"What values/events are arriving over time?"

1. What is the difference between Signals and Observables?

Signals

A Signal is a reactive container that always has a current value.

count = signal(0);

Read it synchronously:

this.count()

Update it:

this.count.set(10);

or:

this.count.update(x => x + 1);

Think:

Signal
  ↓
Current State

count = 10

Observable

An Observable represents a stream of values/events over time.

products$ = this.http.get<Product[]>(
  '/api/products'
);

You normally consume it through:

products$.subscribe(...)

or Angular's AsyncPipe.

An Observable can conceptually produce:

Observable

Time ──────────────────────────────→

       Value1    Value2    Value3
          ↓         ↓         ↓
        event     event     event

Full Simple Program

This example deliberately uses both because that is common in real Angular applications.

product.model.ts

export interface Product {
  id: number;
  name: string;
  price: number;
}

product.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

import { Product } from './product.model';

@Injectable({
  providedIn: 'root'
})
export class ProductService {

  private apiUrl =
    'https://localhost:7001/api/products';

  constructor(private http: HttpClient) {}

  getProducts(): Observable<Product[]> {
    return this.http.get<Product[]>(this.apiUrl);
  }
}

product.ts

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

import { ProductService } from './product.service';
import { Product } from './product.model';

@Component({
  selector: 'app-product',
  standalone: true,
  templateUrl: './product.html'
})
export class ProductComponent {

  products = signal<Product[]>([]);

  searchText = signal('');

  filteredProducts = computed(() => {

    const search =
      this.searchText().toLowerCase();

    return this.products().filter(product =>
      product.name.toLowerCase().includes(search)
    );

  });

  constructor(
    private productService: ProductService
  ) {}

  ngOnInit() {

    this.productService
      .getProducts()
      .subscribe(products => {

        this.products.set(products);

      });
  }

  search(value: string) {
    this.searchText.set(value);
  }
}

product.html

<h2>Products</h2>

<input
  type="text"
  placeholder="Search product"
  (input)="search($any($event.target).value)"
/>

@for (product of filteredProducts(); track product.id) {

  <div>
    {{ product.name }} -
    {{ product.price }}
  </div>

}

How this program works

Notice that we use an Observable for the HTTP operation:

getProducts(): Observable<Product[]> {
  return this.http.get<Product[]>(this.apiUrl);
}

Why?

Because HTTP is an asynchronous operation.

Angular
   ↓
HTTP Request
   ↓
Waiting...
   ↓
ASP.NET Core Web API
   ↓
HTTP Response
   ↓
Observable emits Product[]

Then:

.subscribe(products => {
  this.products.set(products);
});

takes the API result and stores it in:

products = signal<Product[]>([]);

Now products represents our application's current product state.

So:

Observable
     ↓
handles asynchronous API operation
     ↓
Product[]
     ↓
Signal
     ↓
stores current UI state

Then:

filteredProducts = computed(...)

derives UI state from the Signals.

The complete architecture is:

ASP.NET Core Web API
        ↓
    HttpClient
        ↓
Observable<Product[]>
        ↓
   subscribe()
        ↓
signal<Product[]>([])
        ↓
     computed()
        ↓
    Angular UI

This is one possible pattern; Angular also provides RxJS/Signal interop APIs such as toSignal() so you don't always need a manual subscription.


Signals vs Observables

Feature Signal Observable
Main purpose State Async/event streams
Current value Yes Not necessarily
Read value count() Subscribe/AsyncPipe
Update set(), update() Depends on source; Subjects can emit
Synchronous read Yes Stream consumption model
Multiple values over time State changes Yes
Lazy Computed signals are lazy Usually lazy
Operators Limited reactive APIs Rich RxJS operators
map() No RxJS map() Yes
filter() No RxJS filter() Yes
switchMap() No Yes
debounceTime() No Yes
catchError() No RxJS equivalent Yes
Cancellation/composition Not its purpose Strong support
UI state Excellent Possible
HTTP Can hold HTTP result Natural fit with HttpClient
WebSocket/event stream Not primary use Excellent

Important conceptual difference

Suppose:

count = signal(10);

At any time:

this.count()

gives:

10

A Signal represents:

"What is count NOW?"

An Observable is better understood as:

"What values/events are happening OVER TIME?"

For example:

Search Observable

Time ───────────────────────────────→

"S"
      "Sy"
            "Sye"
                    "Syed"

You may want to process this using:

debounceTime()
distinctUntilChanged()
switchMap()

That's where RxJS is particularly powerful.


2. When should you use Signals?

Use Signals primarily when you're managing reactive application or UI state.

Example 1: Counter

count = signal(0);

Perfect Signal use case.


Example 2: Logged-in user state

currentUser = signal<User | null>(null);

You want to know:

Who is the current user?

That's state.


Example 3: Product list

products = signal<Product[]>([]);

The Signal represents the application's current product collection.


Example 4: Selected Product

selectedProduct =
  signal<Product | null>(null);

Again:

"What product is currently selected?"

That's state.


Example 5: Loading state

isLoading = signal(false);

You can then:

this.isLoading.set(true);

and:

this.isLoading.set(false);

Example 6: Search/filter state

searchText = signal('');

Derived state:

filteredProducts = computed(() => {

  const search =
    this.searchText().toLowerCase();

  return this.products().filter(x =>
    x.name.toLowerCase().includes(search)
  );

});

This is an excellent Signal + computed() scenario.


Signals are especially good for

Component state
       ↓
Signals

Selected values
       ↓
Signals

UI flags
       ↓
Signals

Application state
       ↓
Signals

Derived state
       ↓
computed()

Writable dependent state
       ↓
linkedSignal()

Examples:

isLoading = signal(false);

selectedId = signal<number | null>(null);

products = signal<Product[]>([]);

currentUser = signal<User | null>(null);

searchText = signal('');

total = computed(...);

3. When should you use RxJS?

Use RxJS when you're dealing with asynchronous streams, event streams, cancellation, timing, or complex stream composition.


Example 1: HTTP requests

Angular HttpClient returns Observables:

getProducts(): Observable<Product[]> {

  return this.http.get<Product[]>(
    '/api/products'
  );

}

RxJS is a natural fit because HTTP is asynchronous.


Example 2: Search with debounce

Suppose the user types:

S
Sy
Sye
Syed

You don't want four API requests immediately.

RxJS provides:

search$
  .pipe(
    debounceTime(500),
    distinctUntilChanged(),
    switchMap(text =>
      this.productService.search(text)
    )
  )
  .subscribe(...);

Flow:

User types:

S ── Sy ── Sye ── Syed
                  ↓
             wait 500 ms
                  ↓
              switchMap
                  ↓
             HTTP Request

This is an area where RxJS is much more expressive than using Signals alone.


Example 3: Cancel previous HTTP requests

Imagine product search.

User searches:

Laptop

Request starts.

Before it finishes, the user searches:

Laptop Dell

With:

switchMap()

RxJS can unsubscribe from the previous inner request and switch to the latest one.

"Laptop"
    ↓
Request 1
    ↓
   X cancelled/unsubscribed

"Laptop Dell"
    ↓
Request 2
    ↓
latest result

Example 4: Multiple API requests

Suppose you need:

Products API
Categories API
Users API

RxJS provides:

forkJoin({
  products: this.productService.getProducts(),
  categories: this.categoryService.getCategories(),
  users: this.userService.getUsers()
})
.subscribe(result => {

  console.log(result.products);
  console.log(result.categories);
  console.log(result.users);

});

That's stream composition.


Example 5: WebSocket

Imagine:

Server
  ↓
Message 1
  ↓
Message 2
  ↓
Message 3
  ↓
Message 4

This is naturally modeled as a stream:

Time ───────────────────────────→

Msg1   Msg2      Msg3      Msg4

RxJS is an excellent fit.


Example 6: Complex event processing

RxJS provides many operators:

map()
filter()
tap()
catchError()
switchMap()
mergeMap()
concatMap()
exhaustMap()
debounceTime()
distinctUntilChanged()
retry()
combineLatest()
forkJoin()

This is one of RxJS's biggest strengths.

Signals intentionally don't try to duplicate the entire RxJS operator ecosystem.


Signal and RxJS Together

This is the most important point.

Don't think:

Signals
   VS
RxJS

Choose only one

Instead think:

Signals
   +
RxJS

For many Angular applications:

          Backend / Events
                 ↓
               RxJS
                 ↓
      asynchronous operations
                 ↓
              Signals
                 ↓
          application state
                 ↓
             computed()
                 ↓
                 UI

Another Practical Example

For your Angular frontend calling an ASP.NET Core Web API, you might have:

Service

getProducts(): Observable<Product[]> {
  return this.http.get<Product[]>(
    `${this.apiUrl}/products`
  );
}

The service deals naturally with the HTTP Observable.

Then your component/store may maintain:

products = signal<Product[]>([]);

selectedProduct =
  signal<Product | null>(null);

searchText = signal('');

isLoading = signal(false);

And:

filteredProducts = computed(() => {

  return this.products().filter(product =>
    product.name.includes(this.searchText())
  );

});

This gives a clean separation:

ProductService
     ↓
Observable
     ↓
Async/API work

Component / Store
     ↓
Signals
     ↓
Current application state

computed()
     ↓
Derived state

Template
     ↓
UI

What about toSignal()?

Angular provides interoperability between RxJS and Signals.

For example:

products$ =
  this.productService.getProducts();

You can convert an Observable into a Signal:

products = toSignal(
  this.productService.getProducts(),
  {
    initialValue: []
  }
);

Import:

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

Now:

products()

gives the latest value exposed through the Signal.

Conceptually:

HttpClient
    ↓
Observable<Product[]>
    ↓
toSignal()
    ↓
Signal<Product[]>
    ↓
Template

This is useful when the source is RxJS but you want Signal-style consumption in the component/template.


When should I use which?

A practical decision guide:

Do I need to store current state?
        │
        └── YES → Signal


Do I need a value calculated from state?
        │
        └── YES → computed()


Do I need writable state linked to another state?
        │
        └── YES → linkedSignal()


Do I have an async/event stream?
        │
        └── YES → RxJS Observable


Do I need debounce/throttle?
        │
        └── YES → RxJS


Do I need switchMap/mergeMap/concatMap?
        │
        └── YES → RxJS


Do I need complex async composition?
        │
        └── YES → RxJS


Do I have an Observable but want Signal-style
state consumption?
        │
        └── Consider toSignal()

Key Points

Signals

Use Signals for state:

products = signal<Product[]>([]);

selectedProduct =
  signal<Product | null>(null);

isLoading = signal(false);

Signals:

  • have a current value,
  • can be read synchronously,
  • work naturally with Angular reactive state,
  • support set() and update() for writable signals,
  • work with computed() for derived state.

RxJS

Use RxJS for streams and complex asynchronous operations.

Especially when you need:

HTTP streams
WebSockets
debounce
throttle
retry
cancellation/switching
combining streams
complex event processing

Use them together

A common architecture is:

ASP.NET Core API
       ↓
Angular HttpClient
       ↓
Observable
       ↓
RxJS operators
       ↓
Signal
       ↓
computed()
       ↓
Angular Template

Interview Questions and Answers

1. What is the main difference between Signals and Observables?

Answer: Signals primarily represent reactive state with a current value, while Observables represent streams of values/events over time.


2. Do Signals replace RxJS?

Answer: No. Signals simplify reactive state management, while RxJS remains powerful for asynchronous streams and complex event processing.


3. When should you use Signals?

Use Signals for application/UI state such as:

isLoading = signal(false);

selectedProduct =
  signal<Product | null>(null);

products = signal<Product[]>([]);

4. When should you use RxJS?

Use RxJS when dealing with asynchronous/event streams or when you need operators such as:

switchMap()
mergeMap()
debounceTime()
retry()
catchError()
combineLatest()
forkJoin()

5. Can Signals and Observables be used together?

Yes, and this is common.

Observable
    ↓
Async data
    ↓
Signal
    ↓
Current state
    ↓
UI

6. Does a Signal always have a current value?

Yes. For example:

count = signal(10);

You can synchronously retrieve:

count()

and get 10.

An Observable does not inherently provide synchronous access to a current value.


7. Which should I use for HttpClient?

HttpClient returns Observables, so RxJS is the natural mechanism for processing the HTTP operation.

this.http.get<Product[]>(url);

You can then keep it as an Observable or convert/store the resulting data as Signal-based state.


8. What is toSignal()?

toSignal() bridges RxJS and Signals by exposing an Observable's latest emitted value as a Signal.

products = toSignal(
  this.productService.getProducts(),
  { initialValue: [] }
);

9. Signals or BehaviorSubject for application state?

Both can represent reactive state. For Signal-oriented Angular code, Signals provide a simpler synchronous API:

currentUser = signal<User | null>(null);

A BehaviorSubject is useful when the state needs to participate extensively in an RxJS pipeline or be exposed as an Observable stream.


10. Give a real-world example of using both Signals and RxJS.

Answer: In a product search page:

Search input
     ↓
RxJS
     ↓
debounceTime()
     ↓
distinctUntilChanged()
     ↓
switchMap()
     ↓
ASP.NET Core Web API
     ↓
Product[]
     ↓
Signal
     ↓
Current Product State
     ↓
Angular UI

This is a strong interview answer because it captures the division of responsibilities:

Use Signals for state and derived state; use RxJS for asynchronous streams and complex event composition. Use both when the application needs both concerns.

← Back to Articles