We’ve been using RXJS often, and noticed that we had a vague mental model for this reactive / stream-based programming. At the beginning, we thought of streams as “weird arrays”, which is not a very helpful mental model.

The RXJS team has provided a very precise definition of the components of the library, but it doesn’t deal with the mental model. As we say on our team, “this document is a list of ingredients, but they haven’t said what they’re making with them”. (The “omelette vs eggs” communication trap.) We want to know how we should conceive of this thing, not just what it’s made of.

After lots of practice and reading, here’s how we think about RXJS:

Mental Model: Household Plumbing

An observable (stream) is like the plumbing in a house.

An event source is like the city water supply that arrives at the house.

Calling .pipe(...) is like hiring a plumber to install a new faucet or appliance. There might be filters or transformations between the PVC/copper pipe to change or restrict what can come out. (For example, you might have an on-demand hot water heater that changes the temperature of the water that passes through it.)

Calling .subscribe() is like turning on the faucet. You’re ready for stuff to come out, and you probably want to use it for something. You can’t control what comes out; you get what you get. If it’s bad, blame the plumber or the city. You’re just the consumer.

const cityWaterSupply = new Subject<any>();
const kitchenTap = cityWaterSupply.pipe(
  filter(water => doesNotContainPathogens(water)),
  map(water => applyHeat(water)),
  filter(water => doesNotExceed110DegreesFarenheitAccordingToCsaSpecifications(water))
);

kitchenTap.subscribe(water => washHandsWith(water));

cityWaterSupply.next("bad water 🦠");
cityWaterSupply.next("nice water");