2

I have a control which is built from a FormBuilder. I want to put a debounceTime for each keyup but not when the user lost focus of the control.

this._control.valueChanges
    .pipe(
      takeUntil(this.ngUnsubscribe),
      debounceTime(1000),
      distinctUntilChanged()
    )
    .subscribe((value) => {
      // Logic goes here
    });

-------------------------------
onInputChange(event: any): void {

  console.log(this.autoComplete.focus, "1"); // Return true

  setTimeout(() => console.log(this.autoComplete.focus, "2")); // return false

}

So the first console log is true, and the one with the setTimeout is false

It make sense for the keyup to have a debounce time to 1000, so it can prevent multiple call to a backend service, but that does not make sense in case of a lostFocus event.

I may be missing something here but

How to implement different debounceTime for different event on the same control ?

Dipiks
  • 3,818
  • 2
  • 23
  • 39
  • 5
    Unrelated, but `takeUntil(this.ngUnsubscribe)` should always be the last operator as a good practice. https://blog.angularindepth.com/rxjs-avoiding-takeuntil-leaks-fb5182d047ef – Reactgular Jul 16 '19 at 13:19
  • @Reactgular Unrelated, but thanks for your advice ! – Dipiks Jul 16 '19 at 13:22
  • What you mean debounceTime fires for lostFocus and other events? You are subscribing on this._control.valueChanges, how is lostFocus related? – Goga Koreli Jul 16 '19 at 13:22
  • @GogaKoreli Because of the debounceTime, the event "onInputChange" is also delayed, and so the value takes some time to be reflected, I'll update the code so you'll hopefully understand what I meant – Dipiks Jul 16 '19 at 13:26
  • I want to summarize the question. Do you want to subscribe to different events and handle these events with different logic? For example, `onKeyup` will send a request, and `onInputChange` will log something. Am I right? – Andrii Zelenskyi Jul 16 '19 at 13:42
  • You can use `https://www.learnrxjs.io/operators/filtering/debounce.html` by returning `timer(duration)` based on your logic – Bill Cheng Jul 16 '19 at 13:45
  • @AndriiZelenskyi The component is generic (maybe a little too much, duh), but I have to manage it, so, in general: The debounceTime on the keyup will be used to prevent too many requests. And the lostFocus event will be used to do some logic exactly when the user lostFocus. – Dipiks Jul 16 '19 at 13:45
  • @GilleQ. Thank you for the explanation. Did you think about the `input` event instead of the `keyup` event? You check this question https://stackoverflow.com/questions/38502560/whats-the-difference-between-keyup-keydown-keypress-and-input-events to find more info. – Andrii Zelenskyi Jul 16 '19 at 13:52

2 Answers2

1

Ideally you need to differentiate between lostFocus and input keyup.

This is your modified example:

merge(
  this._control.valueChanges.pipe(
    filter(() => this.autoComplete.focus === true),
    debounceTime(1000),
    distinctUntilChanged(),
  ),
  this._control.valueChanges.pipe(
    filter(() => this.autoComplete.focus === false),
    distinctUntilChanged(),
  ),
)
  .pipe(takeUntil(this.ngUnsubscribe))
  .subscribe((value) => {
    // Logic goes here
  });

This way you have merged two version of this._control.valueChanges one which is responsible for keyup and another responsible for focuslost

Goga Koreli
  • 2,807
  • 1
  • 12
  • 31
  • With the pipe which holds the takeUnit I got a `Property 'pipe' does not exist on type 'OperatorFunction<{}, any>'.` I understand your example by reading it, but not technically as I never did that, so I don't know why this error apppear – Dipiks Jul 16 '19 at 14:05
  • 1
    Instead of operator, import static merge, which is: `import { merge } from 'rxjs';` – Goga Koreli Jul 16 '19 at 14:07
1

Thank you for the question!

I think you could simplify the structure of your component with the different "streams" of data. For me, it's easier to think about observables as about streams.

The basic idea is to split your event streams. In this case, you will reduce the coupling of your code. If you do so, you will be able to switch implementation easily in the future.

Here you can find the Stackblitz example to play around with the implementation.

private lostFocusSubject = new Subject<void>();
  private keyUpSubject = new Subject<void>();
  private onDestroySubject = new Subject();
  keyUp$ = this.keyUpSubject.asObservable().pipe(takeUntil(this.onDestroySubject));
  lostFocus$ = this.lostFocusSubject.asObservable().pipe(takeUntil(this.onDestroySubject));

  request$ = this.keyUp$.pipe(
    debounceTime(500),
    switchMap(() => this.getEmulatedRequest()),
    takeUntil(this.onDestroySubject)
  )

  ngOnInit() {
    this.request$.subscribe(() => console.log('Request was processed'));
    this.keyUp$.subscribe(() => console.log('Key up was processed'));
    this.lostFocus$.subscribe(() => console.log('Lost focus was processed'));
  }

  ngOnDestroy() {
    this.onDestroySubject.next();
  }

  onLostFocus() {
    this.lostFocusSubject.next();
  }

  onKeyUp() {
    this.keyUpSubject.next();
  }

  private getEmulatedRequest(): Observable<number> {
    return timer(1000);
  }

Thank you for your question one more time.

I'm waiting for your feedback. Good luck have fun with the demo.

  • It really seems promising, thanks for you explanation, I'll try that as well and give you a feedback after that. – Dipiks Jul 16 '19 at 14:25