I don't know if it's by design, but why when I call unsubscribe on observable, it doesn't send the complete notification? For example:
const sub = timer(1000).subscribe({
complete() {
// this isn't being called
}
});
sub.unsubscribe();
because unsubscribe, unsubscribes you from the event, which means that I am not interested with the result anymore. In which case you cannot expect to fire complete event.
Unsubscribe Disposes the resources held by the subscription. May, for
instance, cancel an ongoing Observable execution or cancel any other
type of work that started when the Subscription was created.
Related
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.
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.
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);
Is there a way to prevent a subscription to a stream (either observable or subject) from completing after calling the error function on the observable/subject?
var subject = new Rx.Subject();
subject.subscribe(function(){alert("OnNext")}, function(){alert("OnError")});
var next = document.querySelector("#next").addEventListener('click', function(){
subject.next();
})
var error = document.querySelector("#error").addEventListener('click', function(){
subject.error();
});
From what I understand, calling subject.error() completes the event stream. So keeping the observable open would break the contract of the Observable object. So how can I handle the lifecycle of the observable in such a way that would recreate the subscription after an error occurs? Here is a plnkr demonstrating the behavior.
Simply said, you can't. At least not with Subjects.
Subjects have internal state and when they emit or receive error notification they mark themself as stopped and will never ever emit anything.
Otherwise, you could use catch() or retry() operators that resubscribe to their source Observable but this wouldn't help you when using Subjects.
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