Understanding the share Operator
The share operator enables a single subscription to the underlying source observable to be shared across multiple subscribers, while also handling the re-subscription process automatically. Under the hood, it relies on a Subject to manage the multicasting, much like the publish and multicast operators do.
Essentially, share is a shorthand for combining the multicast operator (with a factory function) and the refCount operator. This snippet:
const shared = obs.pipe(
share()
);
behaves identically to this one:
const shared = obs.pipe(
multicast(() => new Subject()),
refCount()
);
So, to grasp share, you first need to understand how multicast works with a factory function. That’s the core principle. Just like multicast, share simply inserts a Subject into the observable pipeline. It’s the Subject that does the heavy lifting of sharing — neither share nor multicast does the sharing themselves. Both operators exist merely to streamline the use of the Subject.
How refCount works is quite simple. It maintains an active subscription to the source observable as long as at least one subscriber remains attached to the shared observable. When no subscribers are left, it unsubscribes from the underlying stream. Internally, refCount keeps track of the number of subscriptions to the observable and subscribes to the source when that count exceeds zero. If the count drops to zero, it unsubscribes from the source. This ensures that everything before the refCount operator maintains only a single subscription, regardless of how many subscribers the target observable has.
The refCount operator automates the connection between the shared observable and the source stream:
- When a new subscription occurs and there is no active subscription to the source,
refCounttriggers theconnectmethod on theConnectableObservablecreated bymulticast. - If the shared observable is unsubscribed and no active subscriptions remain,
refCountunsubscribes from the source stream. - When the source completes, all subscriptions to the shared observable receive the
COMPLETEnotification. If a new subscription appears afterwards,refCountre-establishes the connection to the source by callingconnectagain on theConnectableObservable.
This example illustrates how multicast with a factory function and the connect method can re-subscribe to the source after completion:
const log = (index) => (v) => console.log(`subscription ${index}\t: ` + v);
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);
Running this example produces the following output:
subscription 1 : 0
subscription 2 : 0
subscription 1 : 1
subscription 2 : 1
subscription 1 : 2
subscription 2 : 2
underlying stream completed
subscription 3 : 0
subscription 3 : 1
subscription 3 : 2
underlying stream completed
Notice that subscriptions 1 and 2 both receive the values, but the underlying obs observable is only subscribed to once. That single subscription is shared between subscription 1 and subscription 2, which is why only one COMPLETE notification appears before the third subscription begins.
The third subscription, however, is delayed by a timeout and starts after the source has already completed. Because of that, calling connect re-subscribes to the source observable, allowing this third subscription to receive all 3 values before completing again. This explains the two COMPLETE notifications in the output.
That’s precisely the behavior delivered by multicast when paired with a factory function.
You can now automate this re-subscription process by adding the refCount operator:
const shared = obs.pipe(
multicast(() => new Subject()),
refCount()
);
shared.subscribe(log(1));
shared.subscribe(log(2));
With refCount in place, manually calling connect to establish a subscription is no longer necessary. The operator handles it automatically as soon as the first active subscriber subscribes to the shared observable.
As noted earlier, swapping the multicast and refCount pair for share yields exactly the same behavior and output:
const shared = obs.pipe(
share()
);
shared.subscribe(log(1));
shared.subscribe(log(2));
A variant of share exists, called shareReplay, which replaces the plain Subject used by share with a ReplaySubject.
