forkJoin(), combineLatest(), Subject, and BehaviorSubject
These are important RxJS concepts in Angular, but they solve two different kinds of problems:
| Concept | Main purpose |
|---|---|
forkJoin() |
Wait for multiple Observables to complete, then return their final values |
combineLatest() |
Combine the latest values whenever any source changes |
Subject |
Manually emit values to multiple subscribers |
BehaviorSubject |
Subject that also stores the latest/current value |
1. What is forkJoin()?
What is it?
forkJoin() combines multiple Observables and waits until all of them complete.
After all complete, it emits one combined result containing the latest/final value from each Observable, then completes.
forkJoin()= Run multiple Observables → wait for all to complete → emit one combined result.
It is particularly useful for multiple independent HTTP requests.
Full Simple Angular Example
dashboard.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { forkJoin } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class DashboardService {
private http = inject(HttpClient);
getDashboardData() {
return forkJoin({
products:
this.http.get('/api/products'),
customers:
this.http.get('/api/customers'),
orders:
this.http.get('/api/orders')
});
}
}
Component
import { Component, inject } from '@angular/core';
import { DashboardService } from './dashboard.service';
@Component({
selector: 'app-dashboard',
standalone: true,
template: `
<h2>Dashboard</h2>
`
})
export class Dashboard {
private dashboardService = inject(DashboardService);
constructor() {
this.dashboardService
.getDashboardData()
.subscribe(result => {
console.log('Products:', result.products);
console.log('Customers:', result.customers);
console.log('Orders:', result.orders);
});
}
}
How forkJoin() Works
Suppose:
Products API ───────────► Complete
Customers API ─────► Complete
Orders API ─────────────────► Complete
│
▼
forkJoin()
│
▼
Combined Result
Even if Products completes first, forkJoin() waits for Orders and Customers.
Then it emits:
{
products: ...,
customers: ...,
orders: ...
}
Important Point
forkJoin() waits for completion.
If one source never completes:
forkJoin({
api: this.http.get('/api/products'),
timer: interval(1000)
})
interval() never completes by itself.
Therefore forkJoin() never emits.
That's why forkJoin() is an excellent fit for normal HttpClient requests—they normally emit a response and complete.
2. What is combineLatest()?
What is it?
combineLatest() combines multiple Observables and emits whenever any source Observable emits a new value, using the latest value from every source.
combineLatest()= Whenever something changes, give me the latest values from all sources.
Full Simple Example
import { combineLatest, Subject } from 'rxjs';
const category$ = new Subject<string>();
const search$ = new Subject<string>();
combineLatest([
category$,
search$
])
.subscribe(([category, search]) => {
console.log(
'Category:', category,
'Search:', search
);
});
category$.next('Laptop');
search$.next('Dell');
search$.next('HP');
category$.next('Mobile');
Output
Category: Laptop Search: Dell
Category: Laptop Search: HP
Category: Mobile Search: HP
How Does It Work?
First:
category$.next('Laptop');
At this point:
Category = Laptop
Search = ???
No output yet because search$ hasn't emitted.
Then:
search$.next('Dell');
Now both have emitted:
Category = Laptop
Search = Dell
combineLatest() emits:
[Laptop, Dell]
Then:
search$.next('HP');
It remembers:
Category = Laptop
and combines it with:
Search = HP
Result:
[Laptop, HP]
Important Rule of combineLatest()
combineLatest() normally waits until every source has emitted at least once.
Observable A ─── A1 ───────── A2 ──────
Observable B ───────── B1 ───────── B2 ─
│
▼
First combined
result
Before B1, there is no latest value for B.
Therefore no combined value can be produced yet.
3. forkJoin() vs combineLatest()
This difference is extremely important.
forkJoin()
API 1 ─────────────── Complete
API 2 ─────── Complete
API 3 ─────────────────── Complete
│
▼
ONE RESULT
Waits for all to complete.
combineLatest()
A ── A1 ───── A2 ───────── A3 ──
B ───── B1 ───────── B2 ─────────
│ │ │ │
▼ ▼ ▼ ▼
Combined Combined Combined...
Keeps producing combined values when sources change.
Comparison
| Feature | forkJoin() |
combineLatest() |
|---|---|---|
| Waits for all sources to emit | Yes | Yes, initially |
| Waits for all to complete | Yes | No |
| Number of combined emissions | Usually one | Potentially many |
| Uses latest/final values | Final values at completion | Latest current values |
| Good for multiple HTTP calls | Excellent | Sometimes |
| Good for changing streams | No | Excellent |
| Long-lived Observables | Usually unsuitable | Suitable |
Easy memory rule:
forkJoin()
→ Wait until everything FINISHES
combineLatest()
→ React whenever something CHANGES
4. What is a Subject?
What is it?
A Subject is a special type of RxJS Observable that allows us to manually emit values using next() and broadcast those values to multiple subscribers.
A normal Observable is usually consumed like:
observable$.subscribe(...)
With a Subject, you can also produce values:
subject.next(...)
Therefore a Subject is both:
Observable
+
Observer
Full Simple Example
import { Subject } from 'rxjs';
const message$ = new Subject<string>();
message$.subscribe(message => {
console.log('Subscriber 1:', message);
});
message$.subscribe(message => {
console.log('Subscriber 2:', message);
});
message$.next('Hello');
message$.next('Angular');
Output
Subscriber 1: Hello
Subscriber 2: Hello
Subscriber 1: Angular
Subscriber 2: Angular
How Does Subject Work?
message$.next('Hello');
broadcasts:
┌────► Subscriber 1
│
Subject ── Hello ───┼────► Subscriber 2
│
└────► Subscriber 3
All currently subscribed subscribers receive the value.
This is called multicasting.
Subject Does Not Store the Previous Value
This is very important.
const subject = new Subject<string>();
subject.next('Angular');
subject.subscribe(value => {
console.log(value);
});
What is printed?
Nothing
Why?
Because:
Angular emitted
│
▼
No subscriber yet
│
▼
Value missed
Then the subscriber arrives later.
A plain Subject does not automatically replay the previous value.
5. What is a BehaviorSubject?
What is it?
A BehaviorSubject is a special type of Subject that:
- requires an initial value,
- stores the latest value,
- immediately gives the current/latest value to a new subscriber.
BehaviorSubject= Subject + current/latest value.
Full Simple Example
import { BehaviorSubject } from 'rxjs';
const userName$ =
new BehaviorSubject<string>('Guest');
userName$.subscribe(value => {
console.log('Subscriber 1:', value);
});
userName$.next('Syed');
userName$.subscribe(value => {
console.log('Subscriber 2:', value);
});
userName$.next('Admin');
Output
Subscriber 1: Guest
Subscriber 1: Syed
Subscriber 2: Syed
Subscriber 1: Admin
Subscriber 2: Admin
Why Did Subscriber 2 Receive Syed?
Because BehaviorSubject remembers the latest value.
Flow:
Initial value
Guest
│
▼
Subscriber 1
│
▼
Guest
next("Syed")
│
▼
Latest value = Syed
│
├────────► Subscriber 1
│
▼
Subscriber 2 joins
│
▼
Immediately receives
│
▼
"Syed"
That's the key feature.
6. Why Does BehaviorSubject Require an Initial Value?
You must create it like:
const user$ =
new BehaviorSubject<string>('Guest');
Because a BehaviorSubject always represents a current value.
At the beginning, that current value is:
Guest
Later:
user$.next('Syed');
Current value becomes:
Syed
Later:
user$.next('Admin');
Current value becomes:
Admin
Think:
BehaviorSubject
Initial → Guest
↓
next() → Syed
↓
next() → Admin
↓
Current → Admin
7. Real Angular BehaviorSubject Example
A common use is sharing state through a service.
user.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
private userSubject =
new BehaviorSubject<string>('Guest');
user$ =
this.userSubject.asObservable();
setUser(userName: string): void {
this.userSubject.next(userName);
}
}
Component A updates the user:
this.userService.setUser('Syed');
Component B listens:
this.userService.user$
.subscribe(user => {
console.log(user);
});
Flow:
Component A
│
│ setUser("Syed")
▼
UserService
│
▼
BehaviorSubject
│
│ next("Syed")
▼
user$
│
▼
Component B
This is one way to share reactive state between components.
8. Why Use asObservable()?
Notice this pattern:
private userSubject =
new BehaviorSubject<string>('Guest');
user$ =
this.userSubject.asObservable();
We keep the actual Subject private:
private userSubject
because we don't want components doing:
this.userService.userSubject.next(...)
Instead, the service controls updates:
setUser(userName: string) {
this.userSubject.next(userName);
}
Consumers only receive:
user$
Conceptually:
UserService
private BehaviorSubject
│
┌───────┴────────┐
│ │
next() asObservable()
│ │
Service controls Components
updates can subscribe
This gives better encapsulation.
9. Getting the Current Value from BehaviorSubject
BehaviorSubject also exposes its current value.
For example:
const userSubject =
new BehaviorSubject<string>('Guest');
console.log(userSubject.value);
Output:
Guest
Then:
userSubject.next('Syed');
console.log(userSubject.value);
Output:
Syed
You may also encounter:
userSubject.getValue();
However, in reactive Angular code, it is often cleaner to consume the exposed Observable rather than frequently reading the Subject imperatively.
10. Subject vs BehaviorSubject
This is one of the most common RxJS interview questions.
Subject
const subject =
new Subject<string>();
No initial value.
Doesn't give a previous value to a late subscriber.
BehaviorSubject
const subject =
new BehaviorSubject<string>('Guest');
Requires an initial value.
Stores the latest value and immediately emits it to new subscribers.
Subject vs BehaviorSubject Example
Subject
const subject =
new Subject<string>();
subject.next('Angular');
subject.subscribe(value => {
console.log(value);
});
Output:
Nothing
The subscriber arrived too late.
BehaviorSubject
const subject =
new BehaviorSubject<string>('Initial');
subject.next('Angular');
subject.subscribe(value => {
console.log(value);
});
Output:
Angular
Because BehaviorSubject remembered:
Latest value = Angular
11. Subject vs BehaviorSubject Comparison
| Feature | Subject | BehaviorSubject |
|---|---|---|
| Initial value required | No | Yes |
| Stores latest value | No | Yes |
| Late subscriber gets latest value | No | Yes |
Supports next() |
Yes | Yes |
| Multiple subscribers | Yes | Yes |
| Multicast | Yes | Yes |
| Current value available | No | Yes |
| Good for events | Yes | Possible |
| Good for current state | Less suitable | Yes |
12. When Should You Use Subject?
Use a Subject when the information represents an event, where new subscribers do not need the previous event.
For example:
Button clicked
Refresh requested
Modal closed
Notification event
Manual trigger
Imagine:
Doorbell rings
If you arrive five minutes later, you don't necessarily need the old ring.
That's similar to:
Subject
13. When Should You Use BehaviorSubject?
Use a BehaviorSubject when the information represents current state.
Examples:
Current user
Current selected language
Current theme
Current cart state
Current filter
Current application status
For example:
Current logged-in user = Syed
A component created later may still need to know the current user.
That's a good conceptual fit for:
BehaviorSubject
14. Easy Real-World Analogy
Subject = Live Radio
If you start listening at 10:30:
10:00 → Song A
10:10 → Song B
10:20 → Song C
10:30 → You start listening
You don't automatically hear Song A, B, or C.
You only receive future broadcasts.
Subject → future emissions
BehaviorSubject = Current Scoreboard
Imagine:
India: 250/4
You enter the stadium now.
You immediately see:
India: 250/4
Then it changes:
251/4
252/4
253/4
You continue receiving updates.
That's similar to:
BehaviorSubject
It provides:
Current/latest value
+
Future updates
15. forkJoin() vs combineLatest() vs Subjects
Don't confuse these concepts.
forkJoin() and combineLatest() combine Observables.
Observable A ─┐
├──► forkJoin/combineLatest
Observable B ─┘
Subject and BehaviorSubject are themselves Observable-like producers that you can push values into:
next(value)
│
▼
Subject
│
├────► Subscriber A
└────► Subscriber B
So:
forkJoin → Combine completed results
combineLatest → Combine changing values
Subject → Broadcast events
BehaviorSubject→ Broadcast + remember current value
Key Points
forkJoin()waits for all source Observables to complete and then emits one combined result.forkJoin()is very useful for multiple independentHttpClientrequests.- If a source never completes,
forkJoin()may never emit. combineLatest()emits whenever a source changes, after every source has emitted at least once.combineLatest()remembers the latest value from each source.Subjectallows values to be manually emitted withnext().- A
Subjectmulticasts values to its current subscribers. - A plain
Subjectdoes not store the previous value for late subscribers. BehaviorSubjectrequires an initial value.BehaviorSubjectstores the latest value.- A new
BehaviorSubjectsubscriber immediately receives the latest value. Subjectis commonly appropriate for events.BehaviorSubjectis commonly appropriate for current state.- Keeping the Subject private and exposing
asObservable()is a common service encapsulation pattern.
Easy memory rule
forkJoin()
→ Wait until ALL FINISH
combineLatest()
→ React when values CHANGE
Subject
→ EVENT broadcaster
BehaviorSubject
→ CURRENT VALUE + future updates
Interview Questions and Answers
1. What is forkJoin()?
Answer:forkJoin() combines multiple Observables, waits until all complete, then emits one result containing the latest/final value from each source.
2. Why is forkJoin() useful with Angular HttpClient?
Answer:
Normal HTTP Observables emit their response and complete, so forkJoin() can run multiple independent API calls and provide their results together after all have completed.
3. What happens if one forkJoin() source never completes?
Answer:forkJoin() will not emit its combined result because it waits for all sources to complete.
4. What is combineLatest()?
Answer:combineLatest() combines the latest values from multiple Observables and emits a new combined result whenever any source emits, after every source has emitted at least once.
5. forkJoin() vs combineLatest()?
Answer:
forkJoin
→ Wait for all to COMPLETE
→ Usually one combined emission
combineLatest
→ Wait for all to EMIT initially
→ Then emit whenever a source changes
6. What is a Subject?
Answer:
A Subject is a special RxJS Observable that can manually emit values using next() and multicast those values to multiple current subscribers.
7. What is a BehaviorSubject?
Answer:
A BehaviorSubject is a Subject that requires an initial value, stores the latest value, and immediately provides that latest value to new subscribers.
8. What is the main difference between Subject and BehaviorSubject?
Answer:
Subject
→ No initial value
→ Doesn't store latest value
BehaviorSubject
→ Requires initial value
→ Stores latest value
→ Gives latest value to new subscribers
9. When should you use Subject?
Answer:
A Subject is suitable for event-like data where only future emissions matter, such as refresh triggers or notification events.
10. When should you use BehaviorSubject?
Answer:
A BehaviorSubject is suitable when you need to represent current state and new subscribers should immediately receive the latest value.
11. Why expose asObservable() from a service?
Answer:
It prevents consumers from calling next() directly. The service retains control over state changes while components can only subscribe.
private userSubject =
new BehaviorSubject<string>('Guest');
user$ = this.userSubject.asObservable();
12. Short Interview Answer
forkJoinwaits for multiple Observables to complete and returns their final values;combineLatestcontinuously combines their latest values. ASubjectbroadcasts new values to current subscribers, while aBehaviorSubjectadditionally stores the current/latest value and immediately provides it to new subscribers.