Subject, Observable and Observer
Over in Observable’s pipe I talked about supplying water, but that was passive supply. What if I want to add water partway through and have everyone drinking from it get the new water? Say switching from groundwater to spring water, with that sweet taste (well…). That’s where you need Subject. Though I have to split something off here first, namely Observable and Observer. Both of them give you a next when you use them, and they’re easy to mix up. next means operating the water tower, and at the same time it means going to the tower to collect water, that is, the publisher defining the data it sends out and the subscriber receiving data.
// The generic <number> says this stream sends numbers
const observable = new Observable((subscriber: Subscriber<number>) => {
subscriber.next(1); // the next here is the method Subscriber implements
subscriber.complete();
});
// subscribe and test
observable.subscribe({
next: (val) => console.log(val),
complete: () => console.log('Done')
});
Subscriber<T> extends the Observer<T> interface, and that interface says the three methods next, error and complete all have to be implemented:
interface Observer<T> {
next: (value: T) => void;
error: (err: any) => void;
complete: () => void;
}
And the element passed into next was given directly by subscriber.next(1) back when we defined observable, which is why the downstream next: (val) => console.log(val) can get a value, and what it gets is exactly what’s in there. Once that’s clear, we know why Subject is different: it is itself an Observable, and an Observer at the same time.
// 1. build a water tower, groundwater by default
const mySubject = new Subject<string>();
// 2. A comes over to collect water
mySubject.subscribe(data => console.log('A 收到:', data));
// 3. pipe the spring water into the tower!
mySubject.next('山泉水');
// console prints: A 收到: 山泉水
That means what you do with this object isn’t only drawing water anymore, you can also decide what water goes in. And once you add it, every consumer after that is affected. So in practice people don’t usually expose Subject directly, otherwise, with nothing keeping it in check, downstream will change the whole data stream beyond recognition.
Subject does have a “multicast” feature though, which means that when it publishes, it calls back next on all downstream subscribers (the objects that ran subscribe) and pushes the data from mySubject.next('山泉水') over to them. Which is to say, if there’s an object that hasn’t had subscribe triggered by then, not only does it not get this value, it doesn’t get any of the others either. Like a high-pressure broadcast water pipe: when it comes it all sprays out, and after that the valve shuts and you don’t get a drop. The obvious problem this causes can be seen in a scenario like this: an operation under an event-triggered mechanism, where the user clicks and only then does it subscribe. Whoever comes late is left empty-handed with nothing at all.
BehaviorSubject is what comes in handy here. It’s a Subject that keeps acting: it doesn’t just push spring water to everyone on the first round, it also gives a share to whoever shows up later. You could call it “the eager water giver”. Whatever comes up, you ask and he gives, the opposite of Subject’s aloof miss-it-and-it’s-gone personality. Most of the time in real use it’s this one you’re using.
But here’s the problem. What if someone’s mouth is itching for trouble, turns their nose up at spring water and insists on groundwater? The current supply is single, downstream will only ever get spring water, and that’s where ReplaySubject comes in. If the first two are, one, stateless: Subject, come late and you’re empty-handed; and one that only has the latest state: BehaviorSubject, whenever you come it gives you the latest (spring water); then ReplaySubject holds all of the past and present states within a specified range. It lets downstream consumers get the last N pieces of data, so whoever comes to collect water afterwards can get their hands on spring water or groundwater (depending on the range specified when it’s called). If you really do like drinking groundwater, just throw the spring water away yourself; or if you want all of it, no harm in drinking it all. Either way the right to dispose of it goes to downstream. The tower only supplies water, it doesn’t care what you do with it.
Translation note. I wrote this in Chinese. This English version is an LLM translation, so the wording is not mine even though the thinking is. Original: Subject、Observable和Observer.