If it does, not exactly sure why/how?
Let's see how share()
is defined:
function shareSubjectFactory() {
return new Subject<any>();
}
return (source: Observable<T>) => refCount()(multicast(shareSubjectFactory)(source)) as Observable<T>;
First of all,
(source: Observable<T>) => refCount()(multicast(shareSubjectFactory)(source))
is the same as
(source: Observable<T>) => source.pipe(
multicast(shareSubjectFactory),
refCount()
)
multicast
will return a ConnectableObservable
, which is still an Observable
, but, among other things, it exposes a connect
method.
// Inside `multicast` operator
const connectable: any = Object.create(source, connectableObservableDescriptor);
connectable.source = source;
connectable.subjectFactory = subjectFactory;
return <ConnectableObservable<R>> connectable;
Source
Another interesting thing about it is that when subscribed to, the subscriber will be added to the Subject
's list of subscribers and the main source will not be subscribed until connect
is called:
_subscribe(subscriber: Subscriber<T>) {
return this.getSubject().subscribe(subscriber);
}
protected getSubject(): Subject<T> {
const subject = this._subject;
if (!subject || subject.isStopped) {
this._subject = this.subjectFactory();
}
return this._subject!;
}
For example:
const src$ = privateSrc.pipe(
tap(() => console.log('from src')),
share(),
tap(() => console.log('from share()')),
)
When src$
is subscribed:
// Subscriber #1
src$.subscribe(/* ... */)
the subscriber will be added to the Subject
's subscribers list and the source src$
, will be subscribed. Why? Because share
also uses refCount
, which subscribes to the source if a new subscriber is registered when there were no previous active subscribers and will unsubscribe from the source if there are no more active subscribers.
Let's take a look at another example:
const src$ = (new Observable(s => {
console.warn('[SOURCE] SUBSCRIBED')
setTimeout(() => {
s.next(1);
}, 1000);
})).pipe(share());
// First subscriber,
// because it's the first one, `refCount` will to its job and the source will be subscribed
// and this subscriber will be added to the `Subject`'s subscribers list
// note that the source sends the data asynchronously
src$.subscribe(/* ... */)
// The second subscriber
// since the source is already subscribed, `refCount` won't subscribe to it again
// instead, this new subscriber will be added to `Subject`'s list
src$.subscribe(/* ... */)
After 1s
, the source will send the value 1
and the subject will receive that value and will send it to its registered subscribers.
This is how refCount
does its magic:
// When a new subscriber is registered
(<any> connectable)._refCount++;
// `RefCountSubscriber` will make sure that if no more subscribers are left
// the source will be unsubscribed
const refCounter = new RefCountSubscriber(subscriber, connectable);
// Add the subscriber to the `Subject`'s list
const subscription = connectable.subscribe(refCounter);
if (!refCounter.closed) {
(<any> refCounter).connection = connectable.connect();
}
return subscription;
And ConnectableObservable.connect
is defined as follows:
connect(): Subscription {
let connection = this._connection;
if (!connection) {
// If the source wasn't subscribed before
this._isComplete = false;
connection = this._connection = new Subscription();
// Subscribing to the source
// Every notification send by the source will be first received by `Subject`
connection.add(this.source
.subscribe(new ConnectableSubscriber(this.getSubject(), this)));
/* ... */
}
return connection;
}
So, if we have a src$
observable that needs to be subscribed multiple times in the template, we can apply the above mentioned concepts.
However, there is an important aspect that we should be aware of.
If our template looks like this:
<!-- #1 -->
<div *ngIf="src$ | async"></div>
<!-- ... -->
<!-- #2 -->
<div *ngIf="src$ | async"></div>
and src$
:
src$ = store.pipe(select(/* ... */), share())
then, if store
already has the value, it will be retrieved synchronously, meaning that when #1
will be registered, the store
will be subscribed and will send that value, but notice that at that time #2
is not yet subscribed, so it won't receive anything.
If the source
is asynchronous, then we should have no problems, since the subscriptions in the template will most likely by synchronous.
But, when the source is synchronous, you could solve this problem this way:
src$ = store.pipe(
select(/* ... */),
subscribeOn(asyncScheduler),
share()
)
subscribeOn(asyncScheduler)
is roughly the same as delaying the subscription of the source with setTimeout(() => {}, 0)
. But, this allows for #2
to be subscribed so that when the source is finally subscribed, both subscribers will receive that value.