Should all RxJS Subscription need to be unsubscribed? - rxjs

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.

Related

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()

Who should subscribe to NGXS async action - the dispatch action caller or the #Action handler?

I don't know whether this is only a matter of style.
There are at least 2 ways of handling async actions:
subscribe after dispatch
// action is being dispatched and subscribed
this.store.dispatch(new LoadCustomer(customerId)).subscribe(); // <-- subscribe
In the State:
#Action(LoadCustomer)
loadCustomer(context: StateContext<CustomerStateModel>,
customerId: string) {
return this.customerService.loadById(customerId).pipe(
tap(c => context.setState(produce(context.getState(), draft => {
draft.byId[customerId] = c;
})))
); // <-- NO subscribe here, just return the Observable
}
subscribe in #Action handler
// action is being dispatched
this.store.dispatch(new LoadCustomer(customerId)); // <-- no subscribe
In the State:
#Action(LoadCustomer)
loadCustomer(context: StateContext<CustomerStateModel>,
customerId: string) {
this.customerService.loadById(customerId).pipe(
tap(c => context.setState(produce(context.getState(), draft => {
draft.byId[customerId] = c;
})))
).subscribe(); // <-- subscribe is done in action handler
}
Question
Which one is better and why?
Edit / Hint
It turned out that the core issue leading to this question was following:
We had an HttpInterceptor caching "too much" which looked liked if some actions had not been executed. In fact the subscription is already handled correctly by NGXS, but in our case no effect was visible (no request in the network tab).
In our cases the .subscribe() calls could be eliminated. Only where we need to wait for an action to finish, a subscription after the dispatch makes sense.
I think it is somewhat a matter of style, but I'd say (from my usage of NGXS) this is most typical:
On dispatch do this, and only subscribe here if there's some post-action you want to do.
this.store.dispatch(new LoadCustomer(customerId));
And in the state, the option 1 approach, to return the Observable to the NGXS framework and let it handle the subscription itself (see from the docs re: action handling).
Approach number one, as there will be only one subscription and the source component/service will be able to react to it. Subscribing in #Action means that whenever the #Action handled is called then new subscription will be created.

RxJS 6: Why calling value on BehaviorSubject is a bad thing? (according to no-subject-value lint rule) [duplicate]

I have an Angular 2 service:
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
}
}
Everything works great. But I have another component which doesn't need to subscribe, it just needs to get the current value of isLoggedIn at a certain point in time. How can I do this?
A Subject or Observable doesn't have a current value. When a value is emitted, it is passed to subscribers and the Observable is done with it.
If you want to have a current value, use BehaviorSubject which is designed for exactly that purpose. BehaviorSubject keeps the last emitted value and emits it immediately to new subscribers.
It also has a method getValue() to get the current value.
The only way you should be getting values "out of" an Observable/Subject is with subscribe!
If you're using getValue() you're doing something imperative in declarative paradigm. It's there as an escape hatch, but 99.9% of the time you should NOT use getValue(). There are a few interesting things that getValue() will do: It will throw an error if the subject has been unsubscribed, it will prevent you from getting a value if the subject is dead because it's errored, etc. But, again, it's there as an escape hatch for rare circumstances.
There are several ways of getting the latest value from a Subject or Observable in a "Rx-y" way:
Using BehaviorSubject: But actually subscribing to it. When you first subscribe to BehaviorSubject it will synchronously send the previous value it received or was initialized with.
Using a ReplaySubject(N): This will cache N values and replay them to new subscribers.
A.withLatestFrom(B): Use this operator to get the most recent value from observable B when observable A emits. Will give you both values in an array [a, b].
A.combineLatest(B): Use this operator to get the most recent values from A and B every time either A or B emits. Will give you both values in an array.
shareReplay(): Makes an Observable multicast through a ReplaySubject, but allows you to retry the observable on error. (Basically it gives you that promise-y caching behavior).
publishReplay(), publishBehavior(initialValue), multicast(subject: BehaviorSubject | ReplaySubject), etc: Other operators that leverage BehaviorSubject and ReplaySubject. Different flavors of the same thing, they basically multicast the source observable by funneling all notifications through a subject. You need to call connect() to subscribe to the source with the subject.
I had similar situation where late subscribers subscribe to the Subject after its value arrived.
I found ReplaySubject which is similar to BehaviorSubject works like a charm in this case.
And here is a link to better explanation: http://reactivex.io/rxjs/manual/overview.html#replaysubject
const observable = of('response')
function hasValue(value: any) {
return value !== null && value !== undefined;
}
function getValue<T>(observable: Observable<T>): Promise<T> {
return observable
.pipe(
filter(hasValue),
first()
)
.toPromise();
}
const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................
You can check the full article on how to implement it from here.
https://www.imkrish.com/blog/development/simple-way-get-value-from-observable
I encountered the same problem in child components where initially it would have to have the current value of the Subject, then subscribe to the Subject to listen to changes. I just maintain the current value in the Service so it is available for components to access, e.g. :
import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject} from 'rxjs/Subject';
#Injectable()
export class SessionStorage extends Storage {
isLoggedIn: boolean;
private _isLoggedInSource = new Subject<boolean>();
isLoggedIn = this._isLoggedInSource.asObservable();
constructor() {
super('session');
this.currIsLoggedIn = false;
}
setIsLoggedIn(value: boolean) {
this.setItem('_isLoggedIn', value, () => {
this._isLoggedInSource.next(value);
});
this.isLoggedIn = value;
}
}
A component that needs the current value could just then access it from the service, i.e,:
sessionStorage.isLoggedIn
Not sure if this is the right practice :)
A similar looking answer was downvoted. But I think I can justify what I'm suggesting here for limited cases.
While it's true that an observable doesn't have a current value, very often it will have an immediately available value. For example with redux / flux / akita stores you may request data from a central store, based on a number of observables and that value will generally be immediately available.
If this is the case then when you subscribe, the value will come back immediately.
So let's say you had a call to a service, and on completion you want to get the latest value of something from your store, that potentially might not emit:
You might try to do this (and you should as much as possible keep things 'inside pipes'):
serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
.subscribe(([ serviceCallResponse, customer] => {
// we have serviceCallResponse and customer
});
The problem with this is that it will block until the secondary observable emits a value, which potentially could be never.
I found myself recently needing to evaluate an observable only if a value was immediately available, and more importantly I needed to be able to detect if it wasn't. I ended up doing this:
serviceCallResponse$.pipe()
.subscribe(serviceCallResponse => {
// immediately try to subscribe to get the 'available' value
// note: immediately unsubscribe afterward to 'cancel' if needed
let customer = undefined;
// whatever the secondary observable is
const secondary$ = store$.select(x => x.customer);
// subscribe to it, and assign to closure scope
sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
sub.unsubscribe();
// if there's a delay or customer isn't available the value won't have been set before we get here
if (customer === undefined)
{
// handle, or ignore as needed
return throwError('Customer was not immediately available');
}
});
Note that for all of the above I'm using subscribe to get the value (as #Ben discusses). Not using a .value property, even if I had a BehaviorSubject.
Although it may sound overkill, this is just another "possible" solution to keep Observable type and reduce boilerplate...
You could always create an extension getter to get the current value of an Observable.
To do this you would need to extend the Observable<T> interface in a global.d.ts typings declaration file. Then implement the extension getter in a observable.extension.ts file and finally include both typings and extension file to your application.
You can refer to this StackOverflow Answer to know how to include the extensions into your Angular application.
// global.d.ts
declare module 'rxjs' {
interface Observable<T> {
/**
* _Extension Method_ - Returns current value of an Observable.
* Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
*/
value: Observable<T>;
}
}
// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
get <T>(this: Observable<T>): Observable<T> {
return this.pipe(
filter(value => value !== null && value !== undefined),
first());
},
});
// using the extension getter example
this.myObservable$.value
.subscribe(value => {
// whatever code you need...
});
There are two ways you can achieve this.
BehaviorSubject has a method getValue() which you can get the value in a specific point of time.
You can subscribe directly with the BehaviorSubject and you may pass the subscribed value to a class member, field or property.
I wouldn't recommend both approaches.
In the first approach, it's a convenient method you can get the value anytime, you may refer to this as the current snapshot at that point of time. Problem with this is you can introduce race conditions in your code, you may invoke this method in many different places and in different timing which is hard to debug.
The second approach is what most developers employ when they want a raw value upon subscription, you can track the subscription and when do you exactly unsubscribe to avoid further memory leak, you may use this if you're really desperate to bind it to a variable and there's no other ways to interface it.
I would recommend, looking again at your use cases, where do you use it? For example you want to determine if the user is logged in or not when you call any API, you can combine it other observables:
const data$ = apiRequestCall$().pipe(
// Latest snapshot from BehaviorSubject.
withLatestFrom(isLoggedIn),
// Allow call only if logged in.
filter(([request, loggedIn]) => loggedIn)
// Do something else..
);
With this, you may use it directly to the UI by piping data$ | async in case of angular.
A subscription can be created, then after taking the first emitted item, destroyed. In the example below, pipe() is a function that uses an Observable as its input and returns another Observable as its output, while not modifying the first observable.
Sample created with Angular 8.1.0 packages "rxjs": "6.5.3", "rxjs-observable": "0.0.7"
ngOnInit() {
...
// If loading with previously saved value
if (this.controlValue) {
// Take says once you have 1, then close the subscription
this.selectList.pipe(take(1)).subscribe(x => {
let opt = x.find(y => y.value === this.controlValue);
this.updateValue(opt);
});
}
}
You could store the last emitted value separately from the Observable. Then read it when needed.
let lastValue: number;
const subscription = new Service().start();
subscription
.subscribe((data) => {
lastValue = data;
}
);
The best way to do this is using Behaviur Subject, here is an example:
var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5
Another approach, If you want / can to use async await (has to be inside of an async functions) you can do this with modern Rxjs:
async myFunction () {
const currentValue = await firstValueFrom(
of(0).pipe(
withLatestFrom(this.yourObservable$),
map((tuple) => tuple[1]),
take(1)
)
);
// do stuff with current value
}
This will emit a value "Right away" because of withLatestFrom, and then will resolve the promise.

Difference between RxJS5 subscription and observer

I see some question/answers relating to Rx Subscriptions/Observers but they may be for older versions of Rx and also not for RxJS, which may conform to a different API.
I was under the impression that subscriptions/subscribers and observers were all the same. If you look at the docs, they are in different adjacent sections, but seem to be exactly the same:
Observer:
http://reactivex.io/rxjs/manual/overview.html#observer
Subscription:
http://reactivex.io/rxjs/manual/overview.html#subscription
what the heck is the difference? Can someone given an example with a practical difference between the two?
An Observer is a consumer of values delivered by an Observable.
So basically the observer receives the values emitted by a stream.
A Subscription is an object that represents a disposable resource, usually the execution of an Observable.
A subscription is basically just a "fact" that a certain observer currently receives data, if you unsubscribe a subscription, both the stream and the observer will still exist, they are just not connected any more.
A real-world metaphor mixed with pseudo-code: Newspaper
Stream: This would be the production-chain of the newspaper (involing the publishing company creating the content and the printing house printing the paper)
const newsPaper$ = Observable.interval(DAILY)
.switchMapTo(date => publishingCompany.createContent(date))
.switchMapTo(content => printingHouse.printPaper(content))
.publish()
.refCount();
Observer: This would be the reader/recipient, that guy with a bathrobe that picks up the newspaper in his front-yard every morning to read it.
const bathrobeGuy = {
next: newsPaper => readPaper(newsPaper),
error: errorMsg => complainAbout(errorMsg), // the bathrobe guy will be so angry, the he unsubscribes the paper
complete: () => subscribeToDifferentNewsPaper()
}
Subscription: This is the news-paper-subscription - the delivery-boy throwing the newspaper into each front-yard every morning.
// this will activate the "delivery boy"
const paperSubscription = newsPaper$.subscribe(bathrobeGuy);
Unsubscribing: When the bathrobe-guy decides to not want the paper any more, he can unsubscribe the paper and the delivery-boy will not deliver any paper any more. However the observer(the bathrobe-guy) and the newspaper-production still exist, but they have simply no relationship any more.
paperSubscription.unsubscribe();
An Observer is an object with a set of callbacks that are executed when you subscribe to an Observable. In other words, when you call subscribe you pass an object of type Observer. Even when you only pass a callback, internally rxjs is creating an Observer with your callback as the next property. Other properties are error and complete.
A Subscription is the return type of the call subscribe, and its only purpose is to be able to call subscription.unsubscribe() in order to not listen to that subscription anymore. The Observer functions (next, error, complete) will no longer be called.
var myObserver = {
next: (val) => {},
error: (err) => {},
complete: () => {}
};
var mySubscription: Subscription = myObservable.subscribe(myObserver);
// then, if later you want to unsubscribe:
mySubscription.unsubscribe()

checking if rxjs observable is complete

I have a class that when instantiated makes some web service calls, pseudo code below:
Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'))
.flatMap(function () {
return Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'));
});
The same class is listening for an onclick event.
When this even is triggered, if the original web service calls are complete: do something
If they are not complete, wait for them to complete, before doing something.
I was wondering how to achieve this with the rxjs approach? rather than setting variables and using if statements.
I would refer to this as an Asynchronous Gate.
These are actually pretty easy to do with Rx.
You will need to cache the web service calls observable sequences.
Then in other calls that are predicated on these being complete, you simply flatMap off their results.
As these are from Promises I believe the result is retained for late subscribers, but if not then you just need to replay(1) the sequences.
So in psudeo code
var startUpData = Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'))
.flatMap(function () {
return Rx.Observable.fromPromise(jQuery.getJSON('https://api.github.com/users'));
});
var events = Rx.Observable....//Your event wired up here.
//When an event
events
.flatMap(function(evt){
//Wait until the startUpData yeilds, but pass on the evt data instead.
return startUpData.map(function(){ return evt;})
//do something here knowing that your event has fired, but the web services have also completed.
.subscribe();
You can see Matt Barrett explain an Async gate in this video at about 51minutes in to this video - https://youtu.be/Tp5mRlHwZ7M?t=51m30s
You may also want to consider the switch operator incase you don't want overlapping events
I believe withLatestFrom or combineLatest will do what you're asking.
Depending on if you wish to only allow the button to be clicked once with the data provided from the service you could use withLatestFrom. If you wish to allow the button to continued to be clicked using the data previously provided by the service you can use combineLatest
const futureEvent$ = Rx.Observable.timer(3000);
const btnClick$ = Rx.Observable
.fromEvent(document.querySelector('button'), 'click');
const futureAndBtnClick$ = futureEvent$.combineLatest(btnClick$);
futureAndBtnClick$.subscribe(x => console.log('click + future stuff happened'));
jsbin example

Resources