Share operator that doesn't unsubscribe - rxjs

I need to lazy load some infinite streams because they are expensive to start. And I also don't ever want to stop them once they are started for the same reason.
I'm thinking it would be neat if there was a share operator that didn't unsubscribe from the underlying stream ever once it is subscribed for the first time, even when all downstream subscribers unsubscribe.
Right now I'm doing it with a publish and a connect on two different lines, which works alright but just seems clunky and not very rxjs like:
public data$(): Observable<any> {
if (this.lazyData$) {
return this.lazyData$;
}
this.lazyData$ = this.someDataProvider.data$()
.publishReplay(1);
this.lazyData$.connect();
return this.lazyData$;
}
Also I want it to replay the last message to new subscribers as you see :)

The shareReplay operator was added in RxJS version 5.4.0. And, in version 5.5.0 a bug was fixed so that it maintains its history when its subscriber count drops to zero.
With the fix, shareReplay will effect the behaviour you are looking for, as it will now unsubscribe from the source only when the source completes or errors. When the number of subscribers to the shared observable drops to zero, the shared observable will remain subscribed to the source.
The behaviour of shareReplay has changed several times and a summary of the changes - and the reasons for them - can be found in this blog post.

As of RxJS 6.4.0, you can specify shareReplay refCount behavior.
source.pipe(
shareReplay({ bufferSize: 1, refCount: true })
)
shareReplay will unsubscribe from source after all subscribers have unsubscribed.

Related

Do RxJS observers always process all events submitted before the observable is completed?

I want to make sure that all events, which were submitted before complete was invoked on the observable, are logged.
I'm aware that operators exist that stop emission of events (takeUntil, etc.) completely.
The question that I have is whether other operators exist which would lead to emissions not being sent if the complete on the subject is sent too 'early'. Are there cases where it would be beneficial to wait with the completion of the observable until the event was handled by the observer?
For example, are there situations (imagine any other RxJS operator instead of the delay) where the following code ...
const subj = new Subject<string>();
subj.pipe(delay(500))
.subscribe((val) => {
console.log(val);
subj.complete();
});
subj.next('1');
... makes more sense than that ...
const subj = new Subject<string>();
subj.pipe(delay(500))
.subscribe((val) => {
console.log(val);
});
subj.next('1');
subj.complete();
... when the subject should only emit one value?
Or is completing the subject immediately after next always safe in such situations?
If there are other factors I'm not aware of (e.g. synchronous vs. asynchronous execution of code) please mention them as well.
In general,
subj.next('1');
subj.complete();
is safe. As far as I know, none of the existing RxJS operators use a completion to cancel/unsubscribe observables early.
That being said, I can easily create such an operator myself. switchMap will cancel currently running inner observables when it receives it's next emission. You could, for example, create a custom operator that unsubscribes and exists early when it receives a complete event.
If your worried about that, however, you're out of luck. No matter what workaround you imagine, I can probably write an operator that will ruin your code. For example filter(_ => false) will stop the 1 from being emitted before the complete in either of the two cases you've described.
In the end, you and your coworkers must write good code (and test!) and RxJS doesn't change that.

Is it safe/okay to combine take(1) and defaultIfEmpty when subscribing to Observable<boolean>?

I am using the following code to basically ensure that I get a result from an Observable
this.authenticationService.isLoggedIn
.pipe(
take(1),
defaultIfEmpty(false)
)
.subscribe(result => return result);
Somehow it feels wrong to me, maybe because it seems sort of procedural.
Is this method okay? Will this get me in trouble in any way?
If in your code it's fine that this.authenticationService.isLoggedIn completes without an emit - then the code in your question is fine too.
If this.authenticationService.isLoggedIn emits anything at some point of time and completes after - then defaultIfEmpty is redundant.
It all depends on what isLoggedIn does.
It is clear that isLoggedIn returns an Observable.
Now, and Observable can do just 3 things
it can notify, i.e. emit, some data for consumption of Observers which are subscribed
it can raise an error
it can complete
So the first question is: how many times can isLoggedIn notify? Is it just one shot or is it a stream of notifications? If it can emit just one value and then complete, than the take(1) operator is useless.
But there is also the case that isLoggedIn never notifies and just completes. In this case a notification would never be signaled by the observer to its subscriber. Using defaultIfEmpty operator ensures that something is notified even in this case.
So, reading your code I understand that isLoggedIn can behave in these 2 ways
Emit more than once but you are interested only in the first notification
Never notify and just complete, in which case you want false to be returned
If this is not true, it may be the case that your code can be simplified.

Cache results of pipe from BehaviorSubject

Using rxjs, I have an expensive computation that maps the result coming out of a BehaviorSubject:
const obs$ = sameBehaviorSubject.pipe(map(expensiveComputation));
Then in multiple other parts of my code I subscribe to that observable. Each time I subscribe, it re-runs expensiveComputation. How can I prevent that?
You can use shareReplay:
const obs$ = sameBehaviorSubject.pipe(map(expensiveComputation), shareReplay(1));
Now you can subscribe to obs$ multiple times without doing expensiveComputation multiple times.
Update: I've included this in a (very) small library of rxjs utils I've wanted in the past. It is the cache function available in s-rxjs-utils.
#siva636 gave this great answer: add shareReplay(1). It does exactly what I asked.
I add this answer to show another alternative, with one small difference that turned out to be very important for me: add publishReplay(1), refCount(). So the solution is:
const obs$ = someBehaviorSubject.pipe(
map(expensiveComputation),
publishReplay(1),
refCount(),
);
The difference occurs when the last subscriber unsubscribes from obs$: shareReplay(1) will keep its subscription open to someBehaviorSubject, whereas this solution will unsubscribe. I am creating these observables in Angular components that come and go over time, so it's important to me that when they are destroyed they completely clean up after themselves. shareReplay(1) leaked subscriptions that continued to build up over time, this solution does not.

RXJS + Redux Observable - how to merge a new stream?

We've recently picked up Redux Observable and it's been a great way to manage high level action orchestration.
One problem I've recently is responding to the result of a data fetch. We have a generic service function which returns a RXJS observable. Normally we select what ever data we need and subscribe.
I thought this would be fairly natural with redux-observable. Use MapTo on the Epic, and return the RXJS observer with the subsequent select.
From what I can tell Redux-observable doesn't subscribe so nothing happens.
Does anyone have an example of how it should work?
export function redirectUserToEndpointEpic(action$) {
return action$.ofType(LOCATION_CHANGE)
.filter(action=>action.payload.pathname !== '/'))
.mapTo(action=>authService.getObserver() // returns a stream which has not been subscribed to
.select(userData=>userData.defaultPath)
.map(push);
}
This code is actually correct. The dependency was using 'rx' library rather than 'rxjs' which caused a conflict.
Thanks all

Understanding the share() RxJS operator in action together with Rx.Observable.ajax

I am trying to understand the behavior of the RxJS 5 share() operator.
According to tutorials and documentation, share() turns a cold observable into a hot one.
I am trying to see that in action. Here is what I have tried:
const search$ = Rx.Observable.ajax('https://www.googleapis.com/books/v1/volumes?q=javascript').share();
And then the first time I subscribe to it:
search$.subscribe(console.log);
I see from the dev tools that a network request is issued:
Request URL:https://www.googleapis.com/books/v1/volumes?q=javascript
then upon rerunning the same subscription:
search$.subscribe(console.log);
I notice that another network request is also issued.
Can someone please explain why the share() operator is displaying this behavior? I was expecting just one network request to be issued...
P.S. using xxx.publishLast().refCount(); instead of xxx.share(); does the job but my main concern is to understand the share() behavior in the above context.
share is a shortcut for publish().refCount(). The refCount-part means, that the stream is hot/shared as long as there is at least 1 subscriber - howver, it is being reset/cold when there are no subscribers. When your ajax-requests finishes, the stream completes and upon completion of a stream any subscriber is automatically being unsubscribed -> setting the subribers of the stream to 0 and therefor resetting the stream - which is why you are experiencing a second network-quest on any future subscription.
Why does this work with publishLast().refCount()? - with publishLast() you get a stream that never completes, therefor no subscriber is automatically unsubscribed and the stream is never being reset.
For caching HTTP-Responses your approach with publishLast().refCount() is perfectly valid.

Resources