mitsuruog
mitsuruog

Reputation: 1459

Rxjs - cancel debounce in the specific case

Question about rxjs puzzle.

I have the input observable stream and it will emit after 3 secs when I type some.

import { fromEvent, interval } from "rxjs";
import { debounce } from "rxjs/operators";

// input is HTMLInputElement
const input$ = fromEvent(input, "input");

input$
  .pipe(debounce(() => interval(3000)))
  .subscribe(e => console.log(e.target.value));

I would like to make a change to cancel the debounce and emit immediately once the button is clicked. But, if I don't click the button, it will wait 3 secs.

import { fromEvent, interval } from "rxjs";
import { debounce } from "rxjs/operators";

const input$ = fromEvent(input, "input");
// add click observable stream
const click$ = fromEvent(button, "click");

input$
  .pipe(debounce(() => interval(3000)))
  // I can't get this to work in the mix!!
  // .pipe(debounce(() => click$))
  .subscribe(e => console.log(e.target.value));

How can this be achieved?

Upvotes: 0

Views: 278

Answers (4)

Xinan
Xinan

Reputation: 3162

The following would be the simplest in my opinion:

const input$ = fromEvent(input, "input");
const click$ = fromEvent(button, "click");

merge(
  input$.pipe(debounceTime(3000)),
  click$
).pipe(
  map(() => input.value)
).subscribe(val => console.log(val));

https://stackblitz.com/edit/rxjs-8bnhxd

Also, you are essentially "combining" 2 different events here, it doesn't make sense to me to rely on event.target.value, as it could be referring to different things which makes it hard to read.

Upvotes: 0

Andrei Gătej
Andrei Gătej

Reputation: 11979

Here could be another solution I think:

input$
  .pipe(
    debounce(
      () => interval(3000).pipe(takeUntil(buttonClick$))
    )
  )
  .subscribe(e => console.log(e.target.value));

debounce will emit the value that caused the inner observable's subscription, when it either completes/emits a value

// Called when the inner observable emits a value
// The inner obs. will complete after this as well
notifyNext(outerValue: T, innerValue: R,
            outerIndex: number, innerIndex: number,
            innerSub: InnerSubscriber<T, R>): void {
  this.emitValue();
}

// Called when the inner observable completes
notifyComplete(): void {
  this.emitValue();
}

Source code

Upvotes: 0

satanTime
satanTime

Reputation: 13574

sounds like a race operator.

const input$ = fromEvent(input, "input");
const click$ = fromEvent(button, "click");

input$
  .pipe(
    switchMap(value => race(
      click$,
      timer(3000),
    ).pipe(
      take(1),
      mapTo(value),
    )),
  .subscribe(e => console.log(e.target.value));

Upvotes: 1

Fan Cheung
Fan Cheung

Reputation: 11380

Here is the solution to toggle debounce, what you have to do is to convert interval() to a stream that change interval time base on button click Js

import { fromEvent, interval,timer} from 'rxjs';
import { debounce,scan,shareReplay,map,startWith,tap,switchMap} from 'rxjs/operators';

const input = fromEvent(document.getElementById('text'), 'input');

const debounceToggle=fromEvent(document.getElementById('toggle'),'click').pipe(
  scan((acc,curr)=>!acc,false),
  map(on=>on?0:3000),
  startWith(3000),
  shareReplay(1),
  switchMap(value=>interval(value))
  )

  const result = input.pipe(debounce(() => {
  return debounceToggle
}));
result.subscribe(x => console.log(x.target.value));

HTML

<button id="toggle">toggle debounce</button>
<input type="text" id="text"/>

Upvotes: 0

Related Questions