David - ACA Group
David - ACA Group

Reputation: 509

How to finish an active debounceTime in OnDestroy

I am currently saving values when a user changes an input field. I don't want to save the value each time a new character is entered so I'm using the rxjs debounceTime to save after 3000ms (just an example) of no changes.

this.subscription.add(this.form.controls.inputControl.valueChanges
        .pipe(debounceTime(3000))
        .subscribe(value => {
            // execute HTTP call with value
        }));

ngOnDestroy(): void {
    this.subscription.unsubscribe();
}

If the user changes the value and OnDestroy gets called before the 3000ms timer is reached, the call won't get executed no more. I was wondering if there was a way to cancel the active timer and execute all remaining observables before destroying the component.

EDIT: Another option could be that the user gets a warning when there are unsaved changes. Like the way google calendar does when adding a new task and leaving the page

Upvotes: 4

Views: 860

Answers (2)

janc
janc

Reputation: 21

To emit the last value that is pending before unsubscribing, you can force the source observable to complete instead of unsubscribing.

private unsub$ = new Subject<void>();

this.form.controls.inputControl.valueChanges
  .pipe(
    takeUntil(unsub$),
    debounceTime(3000)
  ).subscribe(value => {
    // execute HTTP call with value
  });

ngOnDestroy(): void {
  this.unsub$.next();
}

Please note that takeUntil must be before debounceTime, otherwise the last pending value will not be emitted.

Upvotes: 0

Andrei Gătej
Andrei Gătej

Reputation: 11934

const destroyed = new Subject();
const isTimerActive = new BehaviorSubject(false);

const stop$ = combineLatest(destroyed, isTimerActive)
  .pipe(
    filter(([isDestroyed, isTimerActive]) => isDestroyed && !isTimerActive)
  );

src$.pipe(
  debounce(
    () => (
      // Now if the component is destroyed, it will not unsubscribe from this stream
      isTimerActive.next(true),
      timer(/* ... */)
    )
  ),
  switchMap(v => makeRequest(v)),

  // If the component is destroyed, then after sending this
  // the stream will be unsubscribed
  tap(() => isTimerActive.next(false)),

  takeUntil(stop$)
).subscribe(/* ... */)


ngOnDestroy () {
  this.destroyed.next(true);
  this.destroyed.complete();
}

It's important to note that the timer is declared inactive(isTimerActive.next(false)) only when we've finished all the tasks that involve the value emitted after the delay.

This is because if destroyed is true and we immediately do isTimerActive.next(false), the unsubscription will happen synchronously, meaning that you won't be able to do anything else with that value.

Upvotes: 3

Related Questions