Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Subjects: The Observable Observer

Hello! Welcome to your first lesson on RxJS.

This lesson marks the beginning of our third module, "Subjects and Multicasting." We'll be moving beyond the basic producer-consumer model of Observables to explore how RxJS handles shared events and state.

Given your extensive background in front-end development, you've likely managed shared state and cross-component communication using various patterns. Today, we'll introduce the fundamental RxJS tool for this job: the Subject.

Our learning outcome for this session is to explain the dual nature of a Subject as both an Observable and an Observer. Understanding this is the key to unlocking powerful patterns for state management and event handling in complex applications.

1. The Problem: Unicast Execution

Before we dive into what a Subject is, let's understand the problem it solves. By default, Observables are unicast. This means that each time you subscribe to a plain Observable, you trigger a new, independent execution of the code inside it.

Imagine an Observable that generates a random number. If two different parts of your application subscribe to it, they will each receive a different random number, because each subscription creates a separate execution.

import { Observable } from 'rxjs';

// This Observable produces a new random number for each subscription.
const unicast$ = new Observable<number>(subscriber => {
  console.log('Executing Observable logic...');
  subscriber.next(Math.random());
  subscriber.complete();
});

console.log('Subscribing first time:');
unicast$.subscribe(value => console.log('Subscriber A:', value));

console.log('Subscribing second time:');
unicast$.subscribe(value => console.log('Subscriber B:', value));

// --- Console Output ---
// Subscribing first time:
// Executing Observable logic...
// Subscriber A: 0.12345...
// Subscribing second time:
// Executing Observable logic...
// Subscriber B: 0.67890...

While this is useful for many scenarios (like independent HTTP requests), it's not ideal when you want multiple components to react to the same event or share the same piece of data. We need a way to multicast a single value to many listeners.

To explore this problem further, please read the following section from the article "Being RxJS Subjects Savvy".

Being RxJS Subjects Savvy

This section explains the problem with the default unicast behavior of Observables, setting the stage for why Subjects are necessary.

Please read the section titled 'La Problemo'. It uses a simple analogy and a code example to illustrate how each new subscriber triggers a completely new execution of the Observable.

2. The Solution: The Subject

A Subject is the RxJS solution for multicasting. It's a special type of Observable that acts as a bridge or proxy, allowing a single value to be pushed to multiple Observers simultaneously.

The key to its power is its dual nature:

  1. A Subject is an Observable: You can subscribe to it just like any other Observable to receive values. It maintains an internal list of all its subscribers.
  2. A Subject is an Observer: It has the methods next(value), error(err), and complete(). You can imperatively push values into the Subject, which it will then broadcast to all its current subscribers.

The official RxJS documentation provides the most direct explanation of this concept.

Subject

This is the official documentation for Subject. It clearly defines its dual role and provides a foundational code example.

Read the sections 'What is a Subject?' and 'Every Subject is an Observer.' Pay close attention to the two main points and the first code example, which demonstrates multiple observers receiving the same values from a single Subject.

Let's look at the code example from the documentation, which contrasts sharply with our previous unicast$ example:

import { Subject } from 'rxjs';

const subject = new Subject<number>();

// Both observers subscribe to the SAME Subject instance.
subject.subscribe({
  next: (v) => console.log(`Observer A: ${v}`),
});
subject.subscribe({
  next: (v) => console.log(`Observer B: ${v}`),
});

// We push values INTO the subject using its Observer interface.
subject.next(1);
subject.next(2);

// --- Console Output ---
// Observer A: 1
// Observer B: 1
// Observer A: 2
// Observer B: 2

As you can see, both observers receive the exact same values because they are listening to the same source. The Subject doesn't re-execute for each subscriber; it simply forwards the values it receives.

This video provides a great summary of the key differences between a plain Observable and a Subject.

Observable vs Subject: Understanding the Differences for Interviews | Angular Interview Concepts

This video from Web Tech Talk clearly contrasts the behavior of Observables and Subjects, focusing on their dual nature and the concepts of unicast vs. multicast.

Watch from 01:23 to 02:13 to see the explanation of a Subject as both an Observable and Observer. Then, watch from 02:58 to 04:11 for a clear demonstration of unicasting vs. multicasting.

3. Using a Subject as a Bridge

The true power of a Subject's dual nature becomes apparent when you use it to convert a unicast Observable into a multicast one. You can subscribe the Subject to a source Observable. In this relationship:

  • The Subject acts as the Observer for the source Observable.
  • The Subject then acts as the Observable for any other subscribers.

This effectively creates a proxy or a "bridge" that shares a single source execution with multiple listeners.

Let's revisit our unicast$ example and see how we can use a Subject to make it multicast:

import { Observable, Subject } from 'rxjs';

// The source Observable is still unicast.
const source$ = new Observable<number>(subscriber => {
  console.log('Executing Observable logic...');
  subscriber.next(Math.random());
  subscriber.complete();
});

// We create a Subject to act as our bridge.
const bridgeSubject = new Subject<number>();

// Multiple subscribers listen to the Subject, not the original source.
bridgeSubject.subscribe(value => console.log('Subscriber A:', value));
bridgeSubject.subscribe(value => console.log('Subscriber B:', value));

// The Subject subscribes to the source Observable.
// This triggers the ONE AND ONLY execution.
console.log('Subject is subscribing to the source...');
source$.subscribe(bridgeSubject);

// --- Console Output ---
// Subject is subscribing to the source...
// Executing Observable logic...
// Subscriber A: 0.45678...
// Subscriber B: 0.45678...

The source Observable logic is executed only once. The Subject receives the value and then multicasts it to both Subscriber A and Subscriber B.

This pattern is fundamental to many advanced RxJS use cases. The official documentation has a clear example of this which you should review.

Subject

This section of the RxJS documentation demonstrates how to use a Subject to convert a unicast execution to a multicast one.

In the same document as before, find the sub-heading 'Every Subject is an Observer'. Read the second code example under this heading, where an Observable created with from subscribes to a Subject.

4. A Note on Encapsulation

A common best practice when using Subjects within services or state stores is to hide the "Observer" part (.next(), .error(), .complete()) from the outside world. This prevents other parts of your application from pushing arbitrary values into your stream, enforcing a clearer data flow.

You can achieve this by exposing only the "Observable" part of the Subject using the .asObservable() method.

Why Do We Need an RxJS Subject? And How Do We Create One?

Deborah Kurata explains this professional pattern of hiding the Subject and exposing only its Observable interface.

Watch from 00:32 to 01:24. This clip demonstrates the common pattern of creating a private Subject and exposing a public Observable using asObservable().

Here is a quick example of this pattern in a class:

class EventService {
  // The Subject is private, so only this class can call .next() on it.
  private actionSubject = new Subject<string>();

  // Expose the public, read-only Observable stream.
  public readonly action$ = this.actionSubject.asObservable();

  public dispatchAction(action: string) {
    // This is the controlled way to add values to the stream.
    this.actionSubject.next(action);
  }
}

const eventService = new EventService();

// External code can subscribe and listen...
eventService.action$.subscribe(action => console.log(`Action dispatched: ${action}`));

// ...but CANNOT push values into the stream.
// eventService.action$.next('some-action'); // This would cause a compile-time error.

eventService.dispatchAction('USER_LOGIN');

This pattern is crucial for building predictable and maintainable state management systems, which we'll explore in depth later in the course.

Conclusion

In this lesson, we've established the foundational concept of the Subject.

Key Takeaways:

  • A Subject has a dual nature: it is both an Observable (can be subscribed to) and an Observer (has next, error, and complete methods).
  • This duality allows it to multicast a single stream of values to multiple subscribers, unlike plain Observables which are unicast (one execution per subscriber).
  • You can use a Subject as a "bridge" to convert a cold, unicast Observable into a hot, multicast one by subscribing the Subject to the source Observable.
  • For better encapsulation, it's a best practice to keep the Subject private and expose only its Observable interface using .asObservable().

This understanding of a Subject as a multicast event bus is the first step toward building reactive state management solutions.

Next Up:

In our next lesson, we will look at BehaviorSubject, a specialized type of Subject that is particularly powerful for managing state because it always holds a "current value" and provides it to new subscribers immediately upon subscription.

Can't find a good explanation? Sign up and we'll make it for you

Sign up