Reputation: 585
I have the this method that gets called multiple times when scrolling a table:
setViewportRange(firstRow: number, lastRow: number): void {
this.getViewport(firstRow, lastRow).subscribe(message => {
console.log(message);
});
}
I don't have the control on the calling of setViewportRange method, but I need to debounce it. So I created this debouncedGetViewport method with the help lodash's debounce function:
setViewportRange(firstRow: number, lastRow: number): void {
this.debouncedGetViewport(firstRow, lastRow);
}
debouncedGetViewport = debounce((firstRow, lastRow) => {
return this.getViewport(firstRow, lastRow).subscribe(message => {
console.log(message);
});
}, 1000);
It works! But a collegue asked why I didn't use RxJs debounce instead. So I tried to implement it with RxJs but I can't get it work. The debounceTime has no effect no matter which value is passed! Can you help me to understand why this isn't working? I think I misunderstood something.
setViewportRange(firstRow: number, lastRow: number): void {
this.debouncedGetViewport(firstRow, lastRow);
}
debouncedGetViewport = (firstRow, lastRow) => {
return this.getViewport(firstRow, lastRow)
.pipe(debounceTime(1000))
.subscribe(message => {
console.log(message);
});
};
Thank you!
Upvotes: 2
Views: 1202
Reputation: 691
First of all make sure from memory leak perspective or strange behaviour when subscribing multiple times to this.getViewport
when setViewportRange
is called. You did not know what happens behind this.getViewport
. It can happen that the callback of getViewport.subscribe can be called multiple times. It is good practice to always unsubscribe.
How you can unsubscribe? There are several ways for unsubscribing from an Observable but in your case you can just use the take
operator.
debouncedGetViewport = debounce((firstRow, lastRow) => {
return this.getViewport(firstRow, lastRow).pipe(take(1)).subscribe(message => {
console.log(message);
});
}, 1000);
Here are some resources why you should unsubscribe:
I created a playground based on your example issue and I think I know what do you mean with: "Can you help me to understand why this isn't working".
I guess the console.log is called but the debounceTime
has no effect, right? Please make sure next time that you explain in your issue description exactly what is not working. It can happen that you will be scored with a minus point.
I think here is a good Stack Overflow explanation from Nuno Sousa why your example with debounceTime
is not working!
Consider your logic. You will create a finalized observer for each onChanges. It doesn't debounce because the observer is already finalized and debounce is to prevent emitting one, in the off-chance that another one comes. So it needs at least two emitions to be justifiable (or more ), and that can't happen if the observer is created in the callback.
It seems you are creating with this.getViewport
a finalized (completed) observable which completes right after emitting the first value and thats the reason why debounceTime
has here no effect.
Tip: take(1)
has no effect if the observable arrives already finalized but it is a best practice to always unsubscribe the subscription.
unsubscribe$ = new Subject();
rows$: Subject<{firstRow: number, lastRow: number}> = new Subject();
ngOnInit() {
this.rows$.pipe(
debounceTime(500),
switchMap(({firstRow, lastRow}) => this.getViewport(firstRow, lastRow)),
takeUntil(unsubscribe$)
).subscribe(resultOfGetViewport => {
console.log(resultOfGetViewport);
});
}
setViewportRange(firstRow: number, lastRow: number) {
this.rows$.next({firstRow, lastRow});
}
ngOnDestroy() {
this.unsubscribe$.next();
this.unsubscribe$.complete();
}
I have created for the previous code a Stackblitz example!
In our new solution we do not use a finalized observable because we use a Subject (rows$)
and a Subject can not complete itself as in getViewport
. We must explicitly do it ourselves. We can see this in takeUntil operator. Only when the component is destroyed, so when ngOnDestroy
is called we tell our rows$ observable to complete itself. Last but not least we get our value from getViewport
with switchMap
. Thats it.
You might wonder if the order of debounceTime
and switchMap
makes a difference here. It depends! If this.getViewport
is an expensive operation, then place it right after debounceTime
and if it is very cheap then the order doesn't matter.
Upvotes: 3