RxJS is widely used in every js framework by web developers. It has become one of the hottest libraries in web development today. Angular uses RxJs very extensively. It offers us an Observable class for streaming, but also other subtypes with different properties which, can be confusing for beginners. If you are new to RxJs then, I highly recommend watching fireship.io’s videos on RxJs. In this blog, we are going to compare and see the difference between Observable, Subject, BehaviourSubject, and ReplaySubject.
You can follow along with this blog and play with the code so that you can understand it clearly. Let’s set up our simple project to test RxJs functionalities. We will use the typescript. Download typescript and RxJs globally by running the following commands -
npm install rxjs -g && npm install typescript -g
Create a directory and a file named rxdemo.ts in it. We will write our code to this file. To run the code use command
tsc rxdemo.ts && node rxdemo.js
Observables
Observables are the most widely used functionality provided by Rxjs. It contains two parts. One is Observable, which acts as a consumer, and the other one is the Observer, which is used to feed the Observable.
import { Observable} from 'rxjs';
const observable = new Observable(observer => {
observer.next("1st messsage from the Observable");
setTimeout(() => observer.next('2nd messsage from the Observable after 2 seconds'), 2000);
setTimeout(() => observer.next('3rd messsage from the Observable after 3 seconds'), 3000);
});
observable.subscribe(value => console.log(value));
/*
Output
1st messsage from the Observable
2nd messsage from the Observable after 2 seconds
3rd messsage from the Observable after 3 seconds
*/
We create an Observable by instantiating a class with a function as its constructor parameter. The observable will pass in an Observer object. With this object, we trigger events to emit from observable. Numerous angular APIs like Http module uses observables. As you can see, The Observer object is scoped to its constructor function only. We can not feed Observable from outside of its constructor. To do that we will use the Subject.
Subject
The Subject implements both the Observable and the Observer interfaces. Think of it as a combination of consumer and feeder.
import { Subject } from 'rxjs';
const subject = new Subject();
subject.next('1st messsage from the Subject!');
subject.subscribe(value => console.log(value));
subject.next('2nd messsage from the Subject!');
subject.next('3nd messsage from the Subject!');
/*
Output
2nd messsage from the Subject!
3nd messsage from the Subject!
*/
Creating a subject is very simple. It does not require a constructor function like Observables. We can subscribe to the Subject as well as feed it data by calling .next()
. Now we can consume as well as feed data to the subject. Notice the output. It does not print '1st message from the Subject'
even though we emitted it by calling next()
. This is because we subscribed the subject after we called next()
with '1st message from the subject'
. The subject does not return the current value on Subscription. It triggers only on .next(value)
call and emits the value. If you want the initial value or the current value immediately on Subscription then BehavourSubject does exactly that.
BehaviouSubject
import { BehaviorSubject } from 'rxjs';
const behaviorSubject = new BehaviorSubject('1st message which is an initial value from the BehaviorSubject');
behaviorSubject.subscribe(value => console.log(value));
behaviorSubject.next('2nd message from the BehaviorSubject');
behaviorSubject.next('3rd message from the BehaviorSubject');
/*
Output
1st message which is an initial value from the BehaviorSubject
2nd message from the BehaviorSubject
3rd message from the BehaviorSubject
*/
Notice that BehaviouSubject provides the initial value after subscription. Now, look at the following code.
import { BehaviorSubject } from 'rxjs';
const behaviorSubject = new BehaviorSubject('1st message which is an initial value from the BehaviorSubject');
behaviorSubject.next('I am between 1st message and 2nd message');
behaviorSubject.subscribe(value => console.log(value));
behaviorSubject.next('2nd message from the BehaviorSubject');
behaviorSubject.next('3rd message from the BehaviorSubject');
/*
Output
I am between 1st message and 2nd message
2nd message from theBehaviorSubject
3rd message from the BehaviorSubject
*/
As you can see, there is another value emitted after the initial value and BehaviourSubject is providing us that value instead of the initial value. This is because BehaviourSubject provides us the current value present at the time subscription. If there is no value emitted after initialization then it will emit the initial value. I was very confused about it while learning rxjs for the first time. So I thought it is better to mention it here. Now, what if you don’t want only the current value but instead you want all values at the subscription. This is where ReplaySubject comes into action.
ReplaySubject
import { ReplaySubject } from 'rxjs';
const replaySubject = new ReplaySubject();
replaySubject.next('1st message from the ReplaySubject!');
replaySubject.next('2nd message from the ReplaySubject!');
replaySubject.next('3rd message from the ReplaySubject!');
replaySubject.next('4th message from the ReplaySubject!');
replaySubject.subscribe(value => console.log(value));
replaySubject.next('5th message from the ReplaySubject!');
replaySubject.next('6th message from the ReplaySubject!');
/*
Output
1st message from the ReplaySubject!
2nd message from the ReplaySubject!
3rd message from the ReplaySubject!
4th message from the ReplaySubject!
5th message from the ReplaySubject!
6th message from the ReplaySubject!
*/
ReplaySubject is pretty much self-descriptive. As it indicates it replays all the values before the subscription.
Now let’s change the code slightly. Add number 2
in the constructor of the ReplaySubject class. Let’s run the code.
import { ReplaySubject } from 'rxjs';
const replaySubject = new ReplaySubject(2);
replaySubject.next('1st message from the ReplaySubject!');
replaySubject.next('2nd message from the ReplaySubject!');
replaySubject.next('3rd message from the ReplaySubject!');
replaySubject.next('4th message from the ReplaySubject!');
replaySubject.subscribe(value => console.log(value));
replaySubject.next('5th message from theReplaySubject!');
replaySubject.next('6th message from the ReplaySubject!');
/*
Output
3rd message from the ReplaySubject!
4th message from the ReplaySubject!
5th message from the ReplaySubject!
6th message from the ReplaySubject!
*/
Notice that it only takes the last two values present before the subscription. This is because of the number 2 which we added in the constructor. It does not affect the values emitted after the subscription. This gives enormous flexibility to us to select how many values from before the subscription we need. There is another one that is AsyncSubject. It emits the last value on its completion.
AsyncSubject
import { AsyncSubject } from 'rxjs';
const asyncSubject = new AsyncSubject();
asyncSubject.next('1st message from the AsyncSubject');
asyncSubject.subscribe(value => console.log(value));
asyncSubject.next('2nd message from the AsyncSubject'); //nothing logged
asyncSubject.subscribe(value => console.log(value));
asyncSubject.next('3rd message from the AsyncSubject'); //nothing logged
asyncSubject.complete();
/*
Output
3rd message from the AsyncSubject
3rd message from the AsyncSubject
*/
As you can see, it emits only the last value on its completion. Notice that ‘3rd message from the AsyncSubject’ is logged by both subscriptions.
That’s it, folks. I hope this blog has helped you to clear the confusion between the Observables, Subjects and its different subtypes. Tell me in the comments if I miss anything. Happy coding!!!
See also
- How to Add Dynamic Canonical Links in Angular
- A simple Time Ago pipe to display relative time in Angular