24 Jul 2024
5 min

publish – RxJS Reference

publish

publish operator is a mechanism to introduce a Subject into the observable stream. This makes it possible to share a single subscription to the underlying stream between multiple subscribers.

publish is mostly used as a shortcut for the multicast operator, so to understand publish you need to understand how multicast works.

Here’s how multicast is used to share a subscription to the underlying obs stream between subscription 1 and subscription 2:

const log = (index) => (v) => console.log(`subscription ${index}\t: ` + v);

const obs = interval(200).pipe(
   take(3),
   tap({ complete() { console.log('underlying stream completed') }})
);

const shared = obs.pipe(
   publish()
);

shared.subscribe(log(1));
shared.subscribe(log(2));

shared.connect();

Similarly to multicast, publish operator returns a ConnectableObservable type that needs to be connected to the underlying stream.

Here’s the output of the code snippet above:

subscription 1	: 0
subscription 2	: 0
subscription 1	: 1
subscription 2	: 1
subscription 1	: 2
subscription 2	: 2
underlying stream completed

Note that although both subscriptions got the values, the underlying obs stream was activated (subscribed) only once and shared between subsctiption 1 and subsctipion 2, hence we get only one COMPLETE notification.

To see it more clearly, we could create our own Observable that logs the emitted values:

function producer(observer) {
   let counter = 0;

   const id = setInterval(() => {
       console.log('producer\t\t: ' + counter);
       observer.next(counter);
       counter++;
   }, 200);

   return () => clearInterval(id);
}

const obs = new Observable(producer).pipe(
   take(3),
   tap({ complete() { console.log('underlying stream completed') }})
);

const shared = obs.pipe(
   publish()
);

shared.subscribe(log(1));
shared.subscribe(log(2));

shared.connect();

And the output will be the following:

producer		: 0
subscription 1	: 0
subscription 2	: 0
producer		: 1
subscription 1	: 1
subscription 2	: 1
producer		: 2
subscription 1	: 2
subscription 2	: 2
underlying stream completed

As you can see, we are getting 3 values logged – 2 for each subscription handler and only one for the producer function. This means that the producer function is activated only once and the underlying stream is shared.

As mentioned earlier, publish can be used as a shortcut for the multicast operator with the `Subject, so we can do the replacement and still observe the same output:

const shared = obs.pipe(
   multicast(new Subject())
);

Similarly to multicast, publish simply allowed us to introduce the Subject into the observable chain. It’s the Subject that handles sharing, not the publish or multicast operator. Both of them are there just to simplify the workflow with the Subject.

In the case of the multicast though, you can pass any instance of a Subject yourself. With publish, the underlying Subject is created internally and is never shared.

Note that mulicast operator is more powerful because it can take a factory function that can create a Subject instance on demand multiple times instead of an internal instance created once in the case of publish operator. You can learn more about this behavior here.

In the following example using multicast with a factory function that returns a Subject results in the different output because the underlying subscription is reactivated:

const obs = new Observable(producer).pipe(
   take(3),
   tap({ complete() { console.log('underlying stream completed') }})
);

const shared = obs.pipe(
   multicast(() => new Subject())
) as ConnectableObservable<any>;

shared.subscribe(log(1));
shared.subscribe(log(2));

shared.connect();

setTimeout(() => { shared.subscribe(log(3)); shared.connect() }, 1000);

If you run this code snippet you’ll see the following output:

producer		: 0
subscription 1	: 0
subscription 2	: 0
producer		: 1
subscription 1	: 1
subscription 2	: 1
producer		: 2
subscription 1	: 2
subscription 2	: 2
underlying stream completed
producer		: 0
subscription 3	: 0
producer		: 1
subscription 3	: 1
producer		: 2
subscription 3	: 2
underlying stream completed

Note how the 3rd subscription gets all 3 values and the underlying stream completes two times.

producer		: 0
subscription 3	: 0
producer		: 1
subscription 3	: 1
producer		: 2
subscription 3	: 2
underlying stream completed

That's the effect of the multicast operator with the factory function.

There are a couple of variations of the publish operator that instead of the plain Subject use ReplaySubject or BehaviorSubject. Those operators are publishReplay and publishBehavior.

Frequently asked questions

How do I share a single subscription between multiple subscribers using publish() in RxJS?

Use the publish() operator to insert a Subject into the observable chain, which lets multiple subscribers share one subscription to the source. Subscribe each observer to the returned ConnectableObservable, then call connect() to activate the underlying stream. The source runs once, every subscriber receives the same values, and only one complete notification is emitted. Nothing runs until connect() is called.

What is the difference between publish() and multicast() in RxJS?

publish() is a shortcut for multicast() with a plain Subject, so both share a source the same way. The difference is that multicast() accepts any Subject instance or a factory function that returns a new Subject each time it connects, while publish() creates a single internal Subject that is never shared or replaced. Use multicast() when you need to pass your own Subject or control how it is created.

Why does publish() run the source observable only once for all subscribers?

Because publish() places a Subject between the source and its subscribers, the source is subscribed only once, when connect() is called. The Subject then forwards each emitted value to every subscriber, so the producer function runs a single time no matter how many observers subscribe. Sharing is handled by the underlying Subject itself, not by the publish() operator.

How do I make a multicast source re-run for each new subscriber in RxJS?

Pass a factory function to multicast(), such as multicast(() => new Subject()), instead of using publish(). A factory creates a fresh Subject each time the observable connects, so calling connect() again reactivates the underlying stream. A later subscriber that calls connect() again receives all values from the start, and the source completes a second time. publish() cannot do this because it creates only one internal Subject.

What are publishReplay and publishBehavior in RxJS and how do they differ from publish()?

publishReplay and publishBehavior are variations of publish() that use a ReplaySubject or a BehaviorSubject instead of a plain Subject. This changes what subscribers receive when sharing a source, because the chosen Subject type determines how values are stored and replayed. Both still require connect() to activate the underlying stream.

Share this post

Sign up for our newsletter

Stay up-to-date with the trends and be a part of a thriving community.