Working with Subjects in Angular (RxJS)

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

  1. What is RxJS in Angular
  2. Understanding Observables
  3. What is a Subject
  4. Subject as an Observable and Observer
  5. Creating a Simple Subject
  6. Subscribing to a Subject
  7. Emitting Values with next()
  8. Broadcasting Data to Multiple Subscribers
  9. Unsubscribing from a Subject
  10. Subjects vs Observables
  11. Subjects vs EventEmitters
  12. When to Use a Subject
  13. Real-World Use Case: Cross-Component Communication
  14. Example: Shared Service with Subject
  15. Using Subjects in a DataService
  16. Hot vs Cold Observables Explained
  17. Subjects as Hot Observables
  18. Using next(), error(), and complete() Methods
  19. Multicasting with Subjects
  20. Example: Chat App Simulation
  21. Introduction to BehaviorSubject
  22. BehaviorSubject vs Subject
  23. Example: Remembering Last Emitted Value
  24. ReplaySubject Explained
  25. Example: Caching Multiple Emitted Values
  26. AsyncSubject Explained
  27. Example: Emit Only the Last Value
  28. Converting Observables to Subjects
  29. Using Subjects with Operators (map, filter, etc.)
  30. Example: Transforming Subject Data Streams
  31. Combining Multiple Subjects
  32. Example: Merging and Combining Subjects
  33. Using Subjects for Form Events
  34. Example: Real-Time Search Input with Subject
  35. Handling Errors in Subjects
  36. Subject Cleanup and Memory Management
  37. Using takeUntil for Automatic Unsubscription
  38. Example: Destroying Subject Subscriptions in Components
  39. Common Mistakes When Using Subjects
  40. Best Practices for Using Subjects
  41. Full Working Example: Global Event Bus
  42. 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

FeatureObservableSubject
NatureUnicastMulticast
CreationCreated using new Observable()Created using new Subject()
SubscribersEach gets its own streamAll share the same stream
EmitsAutomatically starts when subscribedManually 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 data
  • error(err) → emit an error
  • complete() → 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

FeatureSubjectBehaviorSubject
Initial ValueNoneRequires one
Last Value ReplayNoYes
Common UseEvent broadcastingState 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

  1. Forgetting to unsubscribe.
  2. Emitting before any subscribers exist.
  3. Using Subject where BehaviorSubject is more appropriate.
  4. Overusing Subjects for all communication.
  5. 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 =&gt; 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);
});

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *