An easier way to understand Subject and Observable in RxJS

I have been reading up and learning RxJS for a while now and one aspect of the library that I couldn’t quite grasp, until now, was the differences and use cases for an Observable and a Subject. Typically when I learn new technology, I like to find a mnemonic device to help me remember a methodology or practice. I finally ran across a brief one from an obscure Stack Overflow comment and figured I would expand on my own version of it.

Quick note, the rest of this post assumes you have a basic knowledge of what RxJS is and how it is used. Learn RxJS has great examples and dives pretty deep for anyone that is curious.

Subject vs. Observable, which to use?

First off, a Subject is an Observable, but comes with some extra functionality. It allows you to call off methods such as next(), error(), complete(), and unsubscribe(). An Observable is more or less just what it says, observing what the Subject does.

Knowing this, if you are using a data set that needs to be observed and ultimately will change over time from actions, then using a Subject is the way to setup the start of the observable process.

1
const data = {
2
  id: 1,
3
  checked: false,
4
};
5
6
const subject = new Subject();
7
const observable$ = new Observable();
8
9
subject.subscribe(value => console.log('Checked:', value.checked));
10
observable$.subscribe(subject);
11
12
subject.next(data);
13
14
data.checked = true;
15
subject.next(data);
16
17
// Checked: false
18
// Checked: true
19

Onto the mnemonic device!

Imagine there is a lion and a nature photographer on the Serengeti. In this scenario, the lion is the Subject while the photographer is the Observable. Since the photographer was paid to shoot photography of the lion, it starts to observe the lion as it is sleeping.

1
const lionData = {
2
  sleeping: true,
3
};
4
5
const lion = new Subject();
6
const photographer$ = new Observable();
7
8
lion.subscribe(data => console.log('Sleeping:', data.sleeping));
9
photographer$.subscribe(lion);
10
11
lion.next(lionData);
12
13
// Sleeping: true
14

In this instance, the photographer is subscribing to the lion and the lion is subscribed to tell if it is sleeping. This is a pretty straight forward example, but should show the simplicity of the relationship.

Adjustment needed

Something odd in this instance is we are defining what the lion is doing after the photographer subscribes to it. To be more true in initialization since we are dealing with living things, RxJS provides a different type of Subject class called BehaviorSubject. The difference with this class is that it allows data to start with on it’s initialization. Let’s adjust the above snippet to use this new class.

1
const lionData = {
2
  sleeping: true,
3
};
4
5
const lion = new BehaviorSubject(lionData);
6
const photographer$ = new Observable();
7
8
lion.subscribe(data => console.log('Sleeping:', data.sleeping));
9
photographer$.subscribe(lion);
10
11
// Sleeping: true
12

With this method, we can be sure that whenever anything subscribes to watching the lion, the lion will be defined and will not throw an undefined error before it gets data. There are also other benefits of using a BehaviorSubject which can be found in the docs.

Expanding on some of the mnemonic

Again, the above was pretty simple and the photographer is probably pretty bored watching a sleeping lion. Let’s instead adjust the script for the photographer to do something and hopefully they get a good photo.

1
const lionData = "Lion sleeping";
2
3
const lion = new BehaviorSubject(lionData);
4
const photographer$ = interval(200);
5
6
lion.subscribe(data => console.log('What is happening:', data));
7
8
photographer$.pipe(
9
  map(photoNumber => `Photographer taking photo: ${photoNum}`),
10
  take(5),
11
).subscribe(lion);
12
13
// What is happening: Lion sleeping
14
// What is happening: Photographer taking photo: 0
15
// What is happening: Photographer taking photo: 1
16
// What is happening: Photographer taking photo: 2
17
// What is happening: Photographer taking photo: 3
18
// What is happening: Photographer taking photo: 4
19

In this example, I have set it up so that the photographer takes a photo every 200 milliseconds interval(200) (they are quick!), but they are only paid to take 5 photos take(5). When the lion first subscribes, it knows what it is doing, sleeping. As soon as the photographer observes and subscribes to the lion, they start taking photos.

This is still pretty boring because the lion is sleeping. Let’s only take photos if the lion wakes up.

Taking interesting photos

One thing to note about the next snippet of code: a subject can not output back up to the observer; it’s a one way flow, just like a stream. Since the lion can not tell the photographer when it wakes up, the photographer must ask by looking into its value via subject.value.

1
const lionData = {
2
  awake: false,
3
};
4
5
const lion = new BehaviorSubject(lionData);
6
const photographer$ = interval(200);
7
8
lion.subscribe(data => console.log('What is happening:', data));
9
10
photographer$.pipe(
11
  map(interval => {
12
    if (lion.value.awake) {
13
      return `Taking photo. Interval ${interval}`;
14
    }
15
    return false;
16
  }),
17
  filter(message => message),
18
  take(5),
19
).subscribe(lion);
20
21
setTimeout(() => {
22
  lionData.awake = true;
23
  lion.next(lionData);
24
}, 600);
25
26
// What is happening: { awake: false } 
27
// What is happening: { awake: true } 
28
// What is happening: Taking photo. Interval 2
29
// What is happening: Taking photo. Interval 3
30
// What is happening: Taking photo. Interval 4
31
// What is happening: Taking photo. Interval 5
32
// What is happening: Taking photo. Interval 6
33

Lot’s of new things are happening here. The photographer still observes the lion every 200 milliseconds, but instead of taking photos, we are going to prevent them from doing so until the lion is awake.

To simplify a conditional check, I am using a data object that states the lion is not awake. This way in my map method, I can check the lion’s value for awake and handle accordingly.

If the lion is not awake, I return the value of false so that my filter operator will prevent the stream from continuing until it gets a truthy value. As soon as it does, the photographer will end up with 5 photos.

To illustrate the point of waiting until the lion wakes up, I set a timeout function to update the value of the lion to awake and invoke the next method of the subject with this new data.

After 2 intervals of the lion being asleep, the lion becomes awake and the photographer can start shooting photos.

A slight change up in the observable

As mentioned at the start, a Subject is an observable type and there is a method to expose it as one. This method, asObservable() can be used for subscribing, but it ends up hiding the identity of the source sequence. You may be asking, “OK, but what does that mean and how can I apply it?” Well, how about a final mnemonic device!

Back to the photographer observable example, say for instance the photographer was sent on assignment, but has not found the lion. They head out to the Serengeti to look around and come across the footprints of an unknown animal and decide to start to follow (observe) it. This is what happens with asObservable.

1
const someAnimal = new Subject();
2
const photographer$ = someAnimal.asObservable();
3
4
photographer$.pipe(
5
  tap(animal => console.log(`I am: ${animal.type}`))
6
  filter(animal => animal.type === 'lion'),
7
).subscribe(data => {
8
  console.log('Taking photos!');
9
});
10
11
someAnimal.next({type: 'elephant'});
12
someAnimal.next({type: 'panther'});
13
someAnimal.next({type: 'lion'});
14
15
// I am: elephant
16
// I am: panther
17
// I am: lion
18
// Taking photos!
19

Finishing up

The RxJS library has a ton of different operators that coincide with the Observable and Subject to act on. In later posts, I plan on providing more mnemonic devices to aid in solidifying learning these operators. Hopefully tying a real life situation to this has helped those that may have been struggling as I was to understand this powerful library.

Filed under: Code