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);