Back to all posts

Subjects Observable in Angular


While Observables are a key tool for handling asynchronous data in Angular, Sometimes you need to have multicasting capabilities (enabling two way data binding). This is where Subjects come into play.

A subject in RxJS is type of Observanle that allows values to be multicast to many Observers. This means that multiple subscribers can listen to the same data stream from a single source.

Key Features of Subjects

  • Multicasting: All subscribers receive the same emitted values.
  • Emit values manually: Subjects allow you to push values explicitly throgh- methods like next().
  • Subject as an Observer: Subjects can be subscribed to and also can emit values through their own subscription.

Different Types of Subjects in RxJS

RxJS provides several types of Subjects Each has its own behavior and use case. Let’s take a closer look at these differenet subject types and when you would use them.

1. Base Subject

The most basic form of Subject is just called Subject. It is the core type and is often used when you need a simple, multicast mechanism. Once you create a Subject, you can emit values using the next() method, and any active subscribers will receive those values.

import { Subject } from 'rxjs';

const subject = new Subject<number>();

subject.subscribe({
  next: (value) => console.log('Subscriber 1:', value)
});

subject.subscribe({
  next: (value) => console.log('Subscriber 2:', value)
});

subject.next(1); // Both subscribers receive '1'
subject.next(2); // Both subscribers receive '2'

In the example above, both subscribers receive the same values because the Subject multicasts the emitted values.

2. BehaviorSubject

A BehaviorSubject is a type of Subject that requires an initial value and always returns the most recent value to new subscribers.

When a new subscribers to a BehaviorSubject it will immediately receive the last emitted value, even if no new values habe emitted since the subscriber’s subscription.

This is useful when you want to have a default state or need to retain the latest value for the subscribers.

import { BehaviorSubject } from 'rxjs';

const behaviorSubject = new BehaviorSubject<number>(0); // Initial value of 0

behaviorSubject.subscribe({
  next: (value) => console.log('Subscriber 1:', value)
});

behaviorSubject.next(1); // Subscriber 1 receives '1'

behaviorSubject.subscribe({
  next: (value) => console.log('Subscriber 2:', value) // Subscriber 2 immediately receives '1'
});

behaviorSubject.next(2); // Both subscribers receive '2'

In this example, Subscriber 2 receives the initial value (0) right away because it subscribes after the first value was emitted.

3. ReplaySubject

A ReplaySubject works similarly to BehaviorSubject, but with a twist: it can remember and replay mutiple past values to new subscribers. When you subscribe to a ReplaySubject , it will replay the specified number of previous values (or all values if no buffer size is specified) to new subscribers.

This is helpful when you want new subscribers to get the most recent state or a history of emitted values.

import { ReplaySubject } from 'rxjs';

const replaySubject = new ReplaySubject<number>(2); // Buffer size of 2

replaySubject.next(1);
replaySubject.next(2);
replaySubject.next(3);

replaySubject.subscribe({
  next: (value) => console.log('Subscriber 1:', value)
});

// Subscriber 1 will receive: 2, 3

In this example, Subscriber 1 will receive the last two emitted values (2 and 3) because of the buffer size of 2.

4. AsyncSubject

An AsyncSubject only emits the last value when the Observable completes. This means that it will not emit values until the Observable is done, and once it completes, it will push the last emitted value to all subscribers.

This is useful when you want to perform an action only after the entire data flow has completed and you only care about the final result.

import { AsyncSubject } from 'rxjs';

const asyncSubject = new AsyncSubject<number>();

asyncSubject.subscribe({
  next: (value) => console.log('Subscriber 1:', value),
  complete: () => console.log('Completed')
});

asyncSubject.next(1);
asyncSubject.next(2);
asyncSubject.complete(); // Subscriber 1 receives '2'

In this example, Subscriber 1 only receives the last emitted value (2) once the complete() method is called, and not the earlier values.

Conclusion: Which Subject Should You Use?

Each type of Subject in RxJS serves a unique purpose, and selecting the right one depends on the behavior you need in your Angular application.

  • Basic Subject: Use when you need to multicast values to multiple subscribers and don’t care about the initial state or last emitted value.
  • BehaviorSubject: Use when you need to provide an initial value and always want the most recent value to be available for new subscribers.
  • ReplaySubject: Use when you need to replay multiple previous values to new subscribers (ideal for maintaining a history of events).
  • AsyncSubject: Use when you only care about the last value when the Observable completes.

In Angular, these Subjects are commonly used for state management, handling user interactions, and managing streams of data (like form inputs, real-time notifications, etc.). Mastering Observables and Subjects will empower you to build more responsive, efficient, and reactive Angular applications.

Real World Project Using Subject

import { Component, OnInit } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { Subject, BehaviorSubject, ReplaySubject, AsyncSubject } from 'rxjs';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  template: `
    <div style="padding: 20px;">
      <h1>RxJS Subjects Demo</h1>

      <!-- Basic Subject Example -->
      <div style="margin-bottom: 20px;">
        <h2>Basic Subject (Chat Messages)</h2>
        <button (click)="sendChatMessage('Hello!')">Send Message</button>
        <div>User 1 Messages: {{ chatMessages1.join(', ') }}</div>
        <div>User 2 Messages: {{ chatMessages2.join(', ') }}</div>
      </div>

      <!-- BehaviorSubject Example -->
      <div style="margin-bottom: 20px;">
        <h2>BehaviorSubject (User Theme Preference)</h2>
        <button (click)="toggleTheme()">Toggle Theme</button>
        <div>Current Theme: {{ currentTheme }}</div>
      </div>

      <!-- ReplaySubject Example -->
      <div style="margin-bottom: 20px;">
        <h2>ReplaySubject (Recent Notifications)</h2>
        <button (click)="addNotification('New notification ' + notificationCount)">
          Add Notification
        </button>
        <div>
          <h3>Latest Notifications:</h3>
          <ul>
            <li *ngFor="let notification of recentNotifications">
              {{ notification }}
            </li>
          </ul>
        </div>
      </div>

      <!-- AsyncSubject Example -->
      <div style="margin-bottom: 20px;">
        <h2>AsyncSubject (Final Score)</h2>
        <button (click)="updateScore(Math.floor(Math.random() * 100))">
          Update Score
        </button>
        <button (click)="completeGame()">Complete Game</button>
        <div>Final Score: {{ finalScore }}</div>
      </div>
    </div>
  `,
  styles: [`
    button {
      margin: 5px;
      padding: 8px 16px;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button:hover {
      background-color: #45a049;
    }
    div {
      margin: 10px 0;
    }
  `],
  imports: [CommonModule]
})
export class App implements OnInit {
  // Basic Subject - Chat System
  private chatSubject = new Subject<string>();
  chatMessages1: string[] = [];
  chatMessages2: string[] = [];

  // BehaviorSubject - Theme Management
  private themeSubject = new BehaviorSubject<string>('light');
  currentTheme: string = 'light';

  // ReplaySubject - Notification System
  private notificationSubject = new ReplaySubject<string>(3); // Keeps last 3 notifications
  recentNotifications: string[] = [];
  notificationCount = 0;

  // AsyncSubject - Game Score
  private scoreSubject = new AsyncSubject<number>();
  finalScore: number = 0;
  Math = Math; // Make Math available in template

  ngOnInit() {
    // Basic Subject Subscribers
    this.chatSubject.subscribe(message => {
      this.chatMessages1.push(message);
    });
    this.chatSubject.subscribe(message => {
      this.chatMessages2.push(message);
    });

    // BehaviorSubject Subscriber
    this.themeSubject.subscribe(theme => {
      this.currentTheme = theme;
    });

    // ReplaySubject Subscriber
    this.notificationSubject.subscribe(notification => {
      this.recentNotifications = [...this.recentNotifications, notification].slice(-3);
    });

    // AsyncSubject Subscriber
    this.scoreSubject.subscribe(score => {
      this.finalScore = score;
    });
  }

  // Basic Subject Methods
  sendChatMessage(message: string) {
    this.chatSubject.next(message);
  }

  // BehaviorSubject Methods
  toggleTheme() {
    const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
    this.themeSubject.next(newTheme);
  }

  // ReplaySubject Methods
  addNotification(message: string) {
    this.notificationCount++;
    this.notificationSubject.next(message);
  }

  // AsyncSubject Methods
  updateScore(score: number) {
    this.scoreSubject.next(score);
  }

  completeGame() {
    this.scoreSubject.complete();
  }
}

bootstrapApplication(App);