Should I unsubscribe after a complete? - rxjs

I have a quick question about observable.
I have the following observable:
getElevation(pos: Cartographic): Observable<Cartographic> {
return new Observable(observer => {
const promise = Cesium.sampleTerrain(this.terrainProvider, 11, Cesium.Cartographic(pos.longitude, pos.latitude))
Cesium.when(promise, (updatedPositions) => {
observer.next(updatedPositions);
observer.complete();
});
});
}
In a component I have:
this.service.getElevation(value).subscribe((e) => {});
My question is, this is a one shoot observable, so I complete just after, is the complete automatically close the subscription? or, do I also have to do this:
const sub = this.service.getElevation(value).subscribe((e) => {sub.unsubscribe();});

In your case you don't need to unsubscribe.
All Observers will automatically be unsubscribed when you call complete. That said, you may want to implement your consuming (component) code do handle the possibility that the implementation of the service may change in the future.
You could do this by using the take operator which will unsubscribe after the first value is emitted:
this.service.getElevation(value).pipe(take(1)).subscribe((e) => {});

You should not unsubscribe in a subscription, it the observable emits instantly then sub is undefined.
If you want a self unsubscribing observable you can use takeUntil
finalise = new Subject();
this.service.getElevation(value).pipe(takeUntil(finalise)).subscribe((e) => {
finalise.next();
finalise.complete();
});

Brief note:
Try to control the subscription with operators such as takeUntil.
You don’t need to unsubscribe yourself if the sender(Subject) completes.
For your case, since the sender returned by getElevation function completes itself after emitting a value one time, you don’t need to either use any operator or unsubscribe yourself to unsubscribe it.
All you have to do: this.service.getElevation(value).subscribe((v) => // do what you want);

Related

Should all RxJS Subscription need to be unsubscribed?

I have the following code in an angular component to capture the keyup events and respond when that happens. The user can navigate away from the page, come back and do the same hundreds of times.
fromEvent(this.input?.nativeElement, 'keyup')
.pipe(
pluck<unknown, string>('target', 'value'),
filter((searchTerm: string) => (searchTerm?.length > 2 || searchTerm?.length == 0)),
throttleTime(200),
debounceTime(300),
distinctUntilChanged()
)
.subscribe(search => {
this.setPageIndex();
this.TriggerLoadUsers(search, 'asc', 0, 10);
});
This is another pattern where an explicit assignment of Subscription is done and then unsubscribed in ngOnDestroy of angular lifecycle method.
public keyupEventsSub$!: Subscription;
this.keyupEventsSub$ = fromEvent(this.input?.nativeElement, 'keyup')
.pipe(
pluck<unknown, string>('target', 'value'),
filter((searchTerm: string) => (searchTerm?.length > 2 || searchTerm?.length == 0)),
throttleTime(200),
debounceTime(300),
distinctUntilChanged()
)
.subscribe(search => {
this.setPageIndex();
this.TriggerLoadUsers(search, 'asc', 0, 10);
});
this.keyupEventsSub$.unsubscribe();
Is there an advantage to following the second pattern where a Subscription is explicitly assigned, subscribed and unsubscribed?
Is there any side effect in using the same pattern for any Observable subscription?
Is there a better pattern where an explicit assignment is not necessary?
1.) Yes, all subscriptions should be unsubscribed to prevent memory leaks. You don't have to unsubscribe from Http calls or Router events because they are one and done and Angular takes care of it for us but I personally still unsubscribe from all subscriptions.
2.) There is no side effect for using the same pattern for any observable subscription. There are many patterns and I will show at the end.
3.) There is a better pattern and I will go from least preferred to most preferred.
Direct subscription assignment. The disadvantage of this is that you will have many subscription variables for every observable stream so it may get out of hand.
// Direct subscription variable (What you have shown)
// don't put a dollar at the end of subscription variable because
// it is a subscription and not an observable
public subscription!: Subscription;
....
this.subscription = this.observable$.subscribe(...);
...
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
Subscription array:
Add every subscription inside of an array.
public subscriptions!: Subscription[];
...
this.subscriptions.push(this.observable$.subscribe(...));
...
ngOnDestroy(): void {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
Async pipe:
One of my favorites but can only be used when presenting data in the HTML and not for event listener (in essence meaning react every time an observable emits).
When the view is presented, the observable will automatically be subscribed to and once the view is destroyed, the subscription is unsubscribed.
count$ = this.otherObservable$.pipe(map(data => data.count));
...
<h1>{{ count$ | async }}</h1>
Destruction subject:
Another one of my favorites and this one is good for subscriptions in the TypeScript class (for event listeners). The beauty of this one is that not too many variables are created and you don't have to deal with an array.
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
....
private destructionSubject$ = new Subject<void>();
...
observable$.pipe(
takeUntil(this.destructionSubject$),
).subscribe(...);
observable2$.pipe(
takeUntil(this.destructionSubject$),
).subscribe(...);
...
ngOnDestroy(): void {
this.destructionSubject$.next();
this.destructionSubject$.complete();
}
There is also another way if all you care about is the first emission and not subsequent emissions:
This can be used for event listeners (react every time this observable emits). This will take the first emission and automatically unsubscribe (the subscription becomes dead).
import { take } from 'rxjs/operators';
....
observable$.pipe(take(1)).subscribe(...);
I hope I answered all of your questions and presented you with good ways to unsubscribe.
Should all RxJS Subscriptions be unsubscribed?
Only Observables that may never error or complete need to be unsubscribed. If you're not sure, it's safer to unsubscribe.
from(promise) is guaranteed to complete or error.
from(['a','r','r','a','y']) is guaranteed to complete.
of(...) is guaranteed to complete.
EMPTY is guaranteed to complete.
NEVER shall never complete or fail.
fromEvent(...) may never complete or fail.
http.get(...) a well written http client should always complete or fail eventually, but there are some (for various technical reasons) which don't. If you're not sure, unsubscribe.
How to unsubscribe
In general, implicit is better than explicit. There are various operators that will unsubscribe for you when a certain condition is met.
take,
takeWhile, and
takeUntil
are the 3 most popular of these. Prefer them over sticking stream.unsubscribe() in our code somewhere.
Doing so keeps all the logic concerning your observable in one place. Making it considerably easier to maintain/extend as the number of observables that you use grows.

Subject-like RxJS Observable that transparently pipes through flatMap

When using Dependency injection in Angular I often need to subscribe to an observable that I haven't yet created!
I often end up using something like this:
// create behavior subject OF Observable<number>
const subject = new BehaviorSubject<Observable<number>>(EMPTY);
// subscribe to it, using flatMap such as to 'unwrap' the observable stream
const unwrappedSubject = subject.pipe(flatMap((x: number) => x));
unwrappedSubject.subscribe(s => console.log(s));
// now actually create the observable stream
const tim = timer(1000, 1000);
// set it into the subject
subject.next(tim);
This uses flatMap to 'unwrap' the observable contained in the subject.
This works fine, but frankly it always feels 'icky'.
What I really want is something like this, where the consumer of the subject treats the instance of the Subject as Observable<number> without having to pipe it every usage.
const subject = new UnwrappingBehaviorSubject<number>(EMPTY);
subject.subscribe((x: number) => console.log(x));
// this could use 'next', but that doesn't feel quite right
subject.setSource(timer(1000, 1000));
I'm aware that I could subscribe to the timer and hook it up directly to the subject, but I also want to avoid an explicit subscribe call because that complicates the responsibility of unsubscribing.
timer(1000, 1000).subscribe(subject);
Is there a nice way to achieve this?
The Subject.ts and BehaviorSubject.ts source files get more complicated than I expected. I'm scared I'll end up with horrible memory leaks if I try to fork it.
I think this would be another way to solve it:
foo.component.ts
export class FooComponent {
private futureObservable$ = new Observable(subscriber => {
// 'Saving' the subscriber for when the observable is ready.
this.futureObservableSubscriber = subscriber;
// The returned function will be invoked when the below mentioned subject instance
// won't have any subscribers(after it had at least one).
return () => this.futureObservableSubscription.unsubscribe();
}).pipe(
// You can mimic the Subject behavior from your initial solution with the
// help of the `share` operator. What it essentially does it to *place*
// a Subject instance here and if multiple subscriptions occur, this Subject instance
// will keep track of all of them.
// Also, when the first subscriber is registered, the observable source(the Observable constructor's callback)
// will be invoked.
share()
);
private futureObservableSubscriber = null;
// We're using a subscription so that it's easier to collect subscriptions to this observable.
// It's also easier to unsubscribe from all of them at once.
private futureObservableSubscription = new Subscription();
constructor (/* ... */) {};
ngOnInit () {
// If you're using `share`, you're safe to have multiple subscribers.
// Otherwise, the Observable's callback(i.e `subscriber => {...}`) will be called multiple times.
futureObservable$.subscribe(/* ... */);
futureObservable$.subscribe(/* ... */);
}
whenObservableReady () {
const tim = timer(1000, 1000);
// Here we're adding the subscription so that is unsubscribed when the main observable
// is unsubscribed. This part can be found in the returned function from the Observable's callback.
this.futureObservableSubscription.add(tim.subscribe(this.futureObservableSubscriber));
}
};
Indeed, a possible downside is that you'll have to explicitly subscribe, e.g in the whenObservableReady method.
With this approach you can also have different sources:
whenAnotherObservableReady () {
// If you omit this, it should mean that you will have multiple sources at the same time.
this.cleanUpCrtSubscription();
const tim2 = timer(5000, 5000);
this.futureObservableSubscription.add(tim2.subscribe(this.futureObservableSubscriber));
}
private cleanUpCrtSubscription () {
// Removing the subscription created from the current observable(`tim`).
this.futureObservableSubscription.unsubscribe();
this.futureObservableSubscription = new Subscription();
}

use RXJS to unsubscribe when a Subject emits specific value

I think I should leverage RXJS for a particular use case. The use case is that I have a subscription that I want to live until a certain value is emitted from a Subject somewhere else.
Eg:
// The sub to unsub when a certain <value> is emitted from a Subject elsewhere.
this.someObservable.subscribe(() => ...)
// Somewhere in the code far, far away, this should kill the subscription(s) that cares about <value>
this.kill.next(<value>)
My go to approach to handle this is caching the subscriptions and then unsubscribing when this.kill.next(<value>) with the relevant <value> is called. Though, that is the imperative approach and feels like it can be done better via takeWhile or some other such technique. Perhaps I might need to merge someObservable with kill Subject ?
How can I leverage RXJS to handle this?
takeUntil is the operator you want
this.someObservable.pipe(
takeUntil(this.kill.pipe(filter(val => val === killValue)))
).subscribe(() => ...)
Once the kill observable emits the killValue it will pass the filter and emit to the takeUntil which unsubscribes the stream.
const { timer, fromEvent, Subject } = rxjs;
const { takeUntil, filter } = rxjs.operators;
const kill$ = new Subject();
kill$.subscribe(val => {
console.log(val);
});
timer(500, 500).pipe(
takeUntil(kill$.pipe(filter(val => val === 'kill')))
).subscribe(val => {
console.log(val);
});
document.getElementById('rnd').addEventListener('click', () => {
kill$.next(Math.random());
});
document.getElementById('kill').addEventListener('click', () => {
kill$.next('kill');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.6.2/rxjs.umd.min.js"></script>
<button id="rnd">Emit random</button>
<button id="kill">Emit kill</button>

RxJS Unsubscribe Only From Inner Observable

Let's say I have an interval that each second sends an heartbeat. At each beat i'd like to inspect something on my web page and react accordingly. I'd also like the option to unsubscribe from the inner Observables actions, but keep getting the heartbeat so when i subscribe back, everything will flow as before.
Creating a Subscription from Interval and piping it leaves no option to unsubscribe from the inner action, but only the whole subscription as whole.
Is there a way to return the inner Observable so i can unsubscribe from it while still retaining the heartbeat created from the Interval?
Edit: I've tried to create a class to describe what I'm talking about:
class Monitor {
sub: Subscription | null = null;
start() {
this.sub = this.monitor().subscribe();
}
monitor() {
const dom$ = someSelectorObserver(this.win.document, '#someSelector').pipe(
mergeMap(newElementOrBail => {
if (newElementOrBail) {
return handle(newElementOrBail);
} else {
return bail();
}
}),
tap({
error: error => this.log.error(error),
}),
);
return dom$;
}
handle(ele: HTMLElement) {
// do stuff
}
bail() {
this.sub.unsubscribe();
}
}
So basically my monitor starts with creating the subscription, as long as there's a new element to handle everything is fine, but when a bail signal appears I'd like to unsubscribe while still monitoring the DOM changes for a return of the previous elements.
So the outer subscription is basically the DOM observer and the inner is the mergeMap handle function. Does it make more sense?
You could just put some conditional on your inner observable:
private takeSignal = true
interval(3000).pipe(switchMap(() => takeSignal ? inner$ : NEVER))
Then just flip takeSignal as needed.
But it seems easier to just unsubscribe from the whole thing and resubscribe when needed. Why keep the interval going when you’re not using it?
You can split your logic in two (or more) streams.
Store heartbeat$ in a separate variable and subscribe to multiple times for different reasons.
In this way, you'd be able to split your logic into different streams and control subscriptions individually.
const heartbeat$ = interval(3000);
const inspectWeb = heartbeat$.pipe(
// do stuff
).subscribe()
inspectWeb.unsubscribe()
heartbeat$.pipe(
// do other stuff
).subscribe()

Rxjs refCount callback to cleanup once every subscribers have unsubscribed?

I've got an observable which is not long lived (http request).
I'm using publishReplay(1) and refCount() so that when there an attempt to access it at the same time, it'll return the same value without triggering the http call again.
But if all the subscriptions are unsubscribed, I need to make some cleanup.
I can't use finalize because:
if I use it before publishReplay then it get closed once the http request is done
if I use it after refCount it'll be run as soon as one observable unsubscribe (instead of when all have unsubscribed)
So basically what I'd like would be to pass a callback to refCount and call that callback when the number of subscriptions reaches 0. But it doesn't work like that. Is there any way to be "warned" when all the subscribers have unsubscribed?
The simplest way I can think of right now would be to create a custom operator that'd pretty much extend refCount to add a callback.
Any better thoughts? I'm pretty sure that there's a better way of doing that.
Thanks!
I am not gonna lie, I was happy to find out I wasn't the only one looking for something like that. There is one another person.
I ended up doing something like that:
import { Observable } from 'rxjs';
export function tapTeardown(teardownLogic: () => void) {
return <T>(source: Observable<T>): Observable<T> =>
new Observable<T>((observer) => {
const subscription = source.subscribe(observer);
return () => {
subscription.unsubscribe();
teardownLogic();
};
});
}
And you use it like:
const augmented = connection.pipe(
tapTeardown(() => /* SOME TEARDOWN LOGIC */),
shareReplay({ bufferSize: 1, refCount: true }),
);
I've tried it and it seems to work correctly.
Here's how it's used:
import { of, timer } from 'rxjs';
import { map, publishReplay, take } from 'rxjs/operators';
import { refCountCb } from './refCountCb';
const source = timer(2000, 10000).pipe(
map(x => `Hello ${x}!`),
publishReplay(1),
refCountCb(() => console.log('MAIN CLOSED'))
);
source.pipe(take(1)).subscribe(x => console.log(x));
source.pipe(take(1)).subscribe(x => console.log(x));
Output:
Hello 0!
Hello 0!
MAIN CLOSED
I've built the custom refCountCb operator based on the source of refCount. It's basically just adding a callback so I won't copy paste the whole code here but it's available on the stackblitz.
Full demo: https://stackblitz.com/edit/rxjs-h7dbfc?file=index.ts
If you have any other idea please share it, I'd be glad to discover different solutions!

Resources