4

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

2 Answers 2

3
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.

1
  • 1
    Very nice solution. I'm still debating using this or a deactivate guard with a warning to that the user is also warned when leaving the page. But thanks for your answer! Commented May 15, 2020 at 9:09
0

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.