Introduction
Reactive programming is a core concept in Angular, powered by the RxJS (Reactive Extensions for JavaScript) library. Among the most powerful constructs RxJS offers are Subjects.
A Subject in RxJS is unique because it acts as both an Observable and an Observer. This means it can emit data (like an Observer) and also be subscribed to (like an Observable).
Subjects are ideal for scenarios where you need to broadcast data to multiple subscribers simultaneously — for example, sharing data between unrelated components, event handling, or building real-time updates in your Angular application.
In this detailed guide, we will explore everything about Subjects: how they work, how to use them effectively, and their variations like BehaviorSubject, ReplaySubject, and AsyncSubject.
Table of Contents
- What is RxJS in Angular
- Understanding Observables
- What is a Subject
- Subject as an Observable and Observer
- Creating a Simple Subject
- Subscribing to a Subject
- Emitting Values with next()
- Broadcasting Data to Multiple Subscribers
- Unsubscribing from a Subject
- Subjects vs Observables
- Subjects vs EventEmitters
- When to Use a Subject
- Real-World Use Case: Cross-Component Communication
- Example: Shared Service with Subject
- Using Subjects in a DataService
- Hot vs Cold Observables Explained
- Subjects as Hot Observables
- Using next(), error(), and complete() Methods
- Multicasting with Subjects
- Example: Chat App Simulation
- Introduction to BehaviorSubject
- BehaviorSubject vs Subject
- Example: Remembering Last Emitted Value
- ReplaySubject Explained
- Example: Caching Multiple Emitted Values
- AsyncSubject Explained
- Example: Emit Only the Last Value
- Converting Observables to Subjects
- Using Subjects with Operators (map, filter, etc.)
- Example: Transforming Subject Data Streams
- Combining Multiple Subjects
- Example: Merging and Combining Subjects
- Using Subjects for Form Events
- Example: Real-Time Search Input with Subject
- Handling Errors in Subjects
- Subject Cleanup and Memory Management
- Using takeUntil for Automatic Unsubscription
- Example: Destroying Subject Subscriptions in Components
- Common Mistakes When Using Subjects
- Best Practices for Using Subjects
- Full Working Example: Global Event Bus
- Conclusion
1. What is RxJS in Angular
RxJS (Reactive Extensions for JavaScript) is a library that helps handle asynchronous data streams using Observables.
Angular’s HttpClient
, FormsModule
, and Router
internally use RxJS for reactive programming.
2. Understanding Observables
An Observable emits data over time and allows other parts of your code to subscribe to those emissions.
Example:
import { Observable } from 'rxjs';
const observable = new Observable(observer => {
observer.next('Hello');
observer.next('World');
});
observable.subscribe(value => console.log(value));
3. What is a Subject
A Subject is both:
- An Observable — you can subscribe to it.
- An Observer — you can emit values into it.
This dual nature makes Subjects perfect for multicasting — sending data to multiple subscribers simultaneously.
4. Subject as an Observable and Observer
Subjects can receive data through next()
and broadcast it to subscribers.
import { Subject } from 'rxjs';
const subject = new Subject<string>();
subject.subscribe(data => console.log('Subscriber 1:', data));
subject.next('Hello');
5. Creating a Simple Subject
You can create a Subject using the RxJS library.
import { Subject } from 'rxjs';
const subject = new Subject<number>();
Now, you can use subject.next()
to emit values and subject.subscribe()
to listen.
6. Subscribing to a Subject
Multiple subscribers can listen to the same Subject.
subject.subscribe(val => console.log('Subscriber A:', val));
subject.subscribe(val => console.log('Subscriber B:', val));
subject.next(10);
subject.next(20);
Output:
Subscriber A: 10
Subscriber B: 10
Subscriber A: 20
Subscriber B: 20
7. Emitting Values with next()
The next()
method pushes values into the Subject stream.
subject.next('Hello RxJS');
subject.next('Angular Subjects');
8. Broadcasting Data to Multiple Subscribers
Unlike Observables (which are unicast), Subjects are multicast.
This means all subscribers receive the same data at the same time.
import { Subject } from 'rxjs';
const subject = new Subject<string>();
subject.subscribe(val => console.log('Subscriber 1:', val));
subject.subscribe(val => console.log('Subscriber 2:', val));
subject.next('Broadcast Message');
Output:
Subscriber 1: Broadcast Message
Subscriber 2: Broadcast Message
9. Unsubscribing from a Subject
Just like Observables, you can unsubscribe to avoid memory leaks.
const sub = subject.subscribe(data => console.log(data));
subject.next('Test');
sub.unsubscribe();
After unsubscribe()
, that listener will no longer receive updates.
10. Subjects vs Observables
Feature | Observable | Subject |
---|---|---|
Nature | Unicast | Multicast |
Creation | Created using new Observable() | Created using new Subject() |
Subscribers | Each gets its own stream | All share the same stream |
Emits | Automatically starts when subscribed | Manually emits via next() |
11. Subjects vs EventEmitters
In Angular, EventEmitter
is used mainly for component events.
However, Subjects are more flexible and can be used anywhere, including services.
12. When to Use a Subject
Use a Subject when:
- You want to share data between multiple components.
- You want to broadcast updates across services.
- You want real-time event updates.
- You are implementing custom event handling.
13. Real-World Use Case: Cross-Component Communication
Imagine you have two unrelated components — Component A and Component B.
When A emits a message, B should receive it instantly.
Subjects provide an easy way to do this.
14. Example: Shared Service with Subject
message.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class MessageService {
private messageSource = new Subject<string>();
currentMessage$ = this.messageSource.asObservable();
sendMessage(msg: string) {
this.messageSource.next(msg);
}
}
component-a.ts
constructor(private messageService: MessageService) {}
send() {
this.messageService.sendMessage('Hello from Component A');
}
component-b.ts
constructor(private messageService: MessageService) {
this.messageService.currentMessage$.subscribe(msg => {
console.log('Received:', msg);
});
}
15. Using Subjects in a DataService
Subjects can also be used for streaming data updates in services like data fetching.
@Injectable({ providedIn: 'root' })
export class DataService {
private usersSubject = new Subject<any[]>();
users$ = this.usersSubject.asObservable();
updateUsers(users: any[]) {
this.usersSubject.next(users);
}
}
16. Hot vs Cold Observables Explained
- Cold Observable: Emits values only when subscribed. Each subscriber gets separate emissions.
- Hot Observable: Emits values whether or not there are subscribers. Subjects are hot.
17. Subjects as Hot Observables
Subjects keep emitting values, and new subscribers will only receive future values — not past ones.
subject.next('First');
subject.subscribe(v => console.log(v));
subject.next('Second');
Output:
Second
18. Using next(), error(), and complete() Methods
Subjects support three methods:
next(value)
→ emit dataerror(err)
→ emit an errorcomplete()
→ complete the stream
Example:
subject.next('Hello');
subject.error('Something went wrong');
subject.complete();
19. Multicasting with Subjects
Subjects allow you to multicast — share the same execution among multiple subscribers.
Example with a simulated stream:
import { interval, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
const source$ = interval(1000);
const stop$ = new Subject<void>();
source$.pipe(takeUntil(stop$)).subscribe(val => console.log(val));
setTimeout(() => stop$.next(), 5000);
20. Example: Chat App Simulation
A Subject can simulate a chat message system.
import { Subject } from 'rxjs';
const chat$ = new Subject<string>();
chat$.subscribe(msg => console.log('User 1:', msg));
chat$.subscribe(msg => console.log('User 2:', msg));
chat$.next('Hello, everyone!');
21. Introduction to BehaviorSubject
A BehaviorSubject is a type of Subject that stores the latest emitted value and replays it to new subscribers.
import { BehaviorSubject } from 'rxjs';
const behavior = new BehaviorSubject<string>('Initial Value');
behavior.subscribe(v => console.log('Subscriber 1:', v));
behavior.next('New Value');
behavior.subscribe(v => console.log('Subscriber 2:', v));
22. BehaviorSubject vs Subject
Feature | Subject | BehaviorSubject |
---|---|---|
Initial Value | None | Requires one |
Last Value Replay | No | Yes |
Common Use | Event broadcasting | State management |
23. Example: Remembering Last Emitted Value
BehaviorSubjects are perfect for state management or data caching.
@Injectable({ providedIn: 'root' })
export class AuthService {
private userSubject = new BehaviorSubject<User | null>(null);
user$ = this.userSubject.asObservable();
login(user: User) {
this.userSubject.next(user);
}
}
24. ReplaySubject Explained
A ReplaySubject replays a specified number of previous values to new subscribers.
import { ReplaySubject } from 'rxjs';
const replay = new ReplaySubject<string>(2);
replay.next('A');
replay.next('B');
replay.next('C');
replay.subscribe(v => console.log('Subscriber:', v));
Output:
B
C
25. Example: Caching Multiple Emitted Values
ReplaySubjects are great for caching past data — for example, keeping a record of last 3 notifications.
26. AsyncSubject Explained
AsyncSubject emits only the last value when the stream completes.
import { AsyncSubject } from 'rxjs';
const async = new AsyncSubject<number>();
async.subscribe(v => console.log('Subscriber 1:', v));
async.next(1);
async.next(2);
async.complete();
Output:
2
27. Example: Emit Only the Last Value
Useful for operations where only the final result matters (e.g., form submission status).
28. Converting Observables to Subjects
You can connect an Observable to a Subject to multicast emissions.
import { fromEvent, Subject } from 'rxjs';
const clicks$ = fromEvent(document, 'click');
const clickSubject = new Subject<MouseEvent>();
clicks$.subscribe(clickSubject);
clickSubject.subscribe(e => console.log('Click:', e.type));
29. Using Subjects with Operators
You can apply RxJS operators to Subject streams.
import { map, filter } from 'rxjs/operators';
subject.pipe(
map(v => v.toUpperCase()),
filter(v => v.length > 5)
).subscribe(console.log);
30. Example: Transforming Subject Data Streams
const data$ = new Subject<number>();
data$.pipe(
map(x => x * 2),
filter(x => x > 10)
).subscribe(val => console.log(val));
data$.next(3);
data$.next(6);
Output:
12
31. Combining Multiple Subjects
You can merge multiple Subjects using merge
or combineLatest
.
import { merge, Subject } from 'rxjs';
const s1 = new Subject<string>();
const s2 = new Subject<string>();
merge(s1, s2).subscribe(data => console.log(data));
s1.next('Hello');
s2.next('World');
32. Example: Merging and Combining Subjects
Combine data from different sources like user input and server updates.
33. Using Subjects for Form Events
Subjects are excellent for handling real-time form inputs.
34. Example: Real-Time Search Input with Subject
import { Subject, debounceTime } from 'rxjs';
const search$ = new Subject<string>();
search$.pipe(debounceTime(300)).subscribe(term => {
console.log('Searching for:', term);
});
search$.next('ang');
search$.next('angular');
35. Handling Errors in Subjects
subject.error('An error occurred');
subject.subscribe({
error: err => console.error(err)
});
36. Subject Cleanup and Memory Management
Always unsubscribe to avoid memory leaks.
37. Using takeUntil for Automatic Unsubscription
import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
private destroy$ = new Subject<void>();
ngOnInit() {
this.dataService.users$.pipe(takeUntil(this.destroy$)).subscribe();
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
38. Example: Destroying Subject Subscriptions in Components
This pattern ensures cleanup of all active subscriptions when the component is destroyed.
39. Common Mistakes When Using Subjects
- Forgetting to unsubscribe.
- Emitting before any subscribers exist.
- Using Subject where BehaviorSubject is more appropriate.
- Overusing Subjects for all communication.
- Mixing hot and cold observables incorrectly.
40. Best Practices for Using Subjects
- Use
BehaviorSubject
for shared states. - Use
ReplaySubject
for caching past values. - Use
AsyncSubject
for one-time completion events. - Always unsubscribe or use
takeUntil
. - Avoid pushing complex logic inside
next()
calls. - Prefer reactive composition (operators) over manual emits.
41. Full Working Example: Global Event Bus
event-bus.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class EventBusService {
private event$ = new Subject<{ name: string; payload?: any }>();
emit(eventName: string, payload?: any) {
this.event$.next({ name: eventName, payload });
}
on(eventName: string) {
return this.event$.asObservable()
.pipe(filter(event => event.name === eventName));
}
}
component-a.ts
this.eventBus.emit('userUpdated', { name: 'Alice' });
component-b.ts
this.eventBus.on('userUpdated').subscribe(data => {
console.log('Received:', data);
});
Leave a Reply