Search code examples
angularrxjs

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


Solution

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