Angular Reactive Forms: Debounce only some specific Form Control
Asked Answered
I

8

25

I have a simple Search Component which contains a Reactive Form with 2 elements:

  • Text Input (to search for arbitrary matching text)
  • Checkbox (to include / exclude deleted results)

So far I use myFormGroup.valueChanges.subscribe(...) to execute my search method.

Now the problem is, that I want to debounce the text input. And at the same time not debounce the checkbox, so the search method is getting executed instantly when clicking the checkbox.

Using valueChanges.debounceTime(500) will of course debounce the whole form. That's not what I want.

This is a stripped down example. The real form has some more inputs. Some should be debounced and some shouldn't.

Is there any easy way to get this done? Or do I have to subscribe to every form control separately?

Would be nice to see how you did solve this.

Thanks in advance.


EDIT: Code

export class SearchComponent {

  myFormGroup: FormGroup;

  constructor(fb: FormBuilder) {
    this.myFormGroup = fb.group({
      textInput: '',
      checkbox: false
    });
  }

  ngOnInit() {
    this.myFormGroup.valueChanges.subscribe(val => {
      // debounce only the textInput,
      // and then execute search
    });
  }

}
Insecurity answered 4/3, 2018 at 0:19 Comment(2)
stackblitz.com/edit/angular-949zs3?file=app/app.component.tsSwap
Looks quite hacky, but seems to work. Another idea: Maybe there's some way to see which value inside the FormGroup changed? Then I could do something like valueChangesDiff.debounce(diff => diff.textInput ? 500 : 0)Insecurity
C
7

Debounce the text control's valueChanges observable, then use combineLatest() to combine it with the checkbox control's valueChanges observable.

Simple example

Celanese answered 4/3, 2018 at 0:40 Comment(5)
My real form has 4 Text Inputs and lot's of Checkboxes and Selects. I don't want combineLatest with over 20 form controls. There must be another way.Insecurity
I'll quote myself: "This is a stripped down example. The real form has some more inputs. Some should be debounced and some shouldn't."Insecurity
Sorry, I missed that in the question.Celanese
But thank you, I'll highlight this part of the question.Insecurity
I created a function that does exactly this, but in an easy way for lots of form controls: stackblitz.com/edit/…Retinoscope
B
39

Create each individual FormControl before adding them to the form group and then you can control the valueChanges observable per FormControl

import { debounceTime, distinctUntilChanged } from "rxjs/operators";

this.textInput.valueChanges
      .pipe(
        debounceTime(400),
        distinctUntilChanged()
      )
      .subscribe(res=> {
        console.log(`debounced text input value ${res}`);
      });

the distinctUntilChanged will make sure only when the value is diffrent to emit something.

Brakpan answered 4/3, 2018 at 9:16 Comment(5)
Create each individual FormControl before but he already has all controls and he already can control valueChanges per FormControlSwap
that's true, I just offered the way I usually do that.Brakpan
import { debounceTime, distinctUntilChanged } from "rxjs/operators"; Your welcome :)Thirtyone
In angular form control can we have input debounce time without using valueChanges observable?Zacheryzack
@ZiaKhan You need to bounce/debounce based on some change or something similar, of course you can do it the native way. something like this freecodecamp.org/news/javascript-debounce-exampleBrakpan
D
8

Debounce for a single control in a form group can be done by

   this.form.get('textInput')
  .valueChanges
  .pipe(debounceTime(500))
  .subscribe(dataValue => {
    console.log("dataValue", dataValue);
  });
Digged answered 10/1, 2020 at 9:32 Comment(0)
C
7

Debounce the text control's valueChanges observable, then use combineLatest() to combine it with the checkbox control's valueChanges observable.

Simple example

Celanese answered 4/3, 2018 at 0:40 Comment(5)
My real form has 4 Text Inputs and lot's of Checkboxes and Selects. I don't want combineLatest with over 20 form controls. There must be another way.Insecurity
I'll quote myself: "This is a stripped down example. The real form has some more inputs. Some should be debounced and some shouldn't."Insecurity
Sorry, I missed that in the question.Celanese
But thank you, I'll highlight this part of the question.Insecurity
I created a function that does exactly this, but in an easy way for lots of form controls: stackblitz.com/edit/…Retinoscope
G
7

Right now I had that problem, I solved it as follows!

// Reactive control
fieldOne = this.formBuilder.control('');

// Reactive form
formGroup = this.formBuilder.group({
  fieldOne: [],
  fieldTwo: [],
  fieldX: [],
});

this.fieldOne.valueChanges
  .pipe(debounceTime(300))
  .subscribe(value => {
    this.formGroup.get('fieldOne').setValue(value);
  });

you have response speed in the controls within the form group and a delay in the events emitted from the individual control, hopefully in the future that the valueChanges emitted by the formGroup will present the control that triggered the event and be able to use filter in the observable

Regards!

Gunplay answered 9/7, 2020 at 2:21 Comment(0)
H
2

You can use the following code that:

  • Uses startWith to emit an initial value, so any form changes immediately trigger an emission
  • Groups the current and previous values
  • Checks if the change is in a field we want to delay and if so, debounce by a set time
this.formGroup.valueChanges
.pipe(
     startWith(this.formGroup.value),
     pairwise(),
     debounce(([previous, current]) => previous.textInput !== current.textInput ? timer(500) : timer(0))
).subscribe(([_, current]) => this.usefullCallback(current));
Horsehide answered 13/3, 2023 at 17:30 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Verst
P
2

You can get valueChanges on your formGroup and add a debounceTime (or debounce) only if a certain field change:

import { startWith } from 'rxjs/operators';
import { debounce, pairwise, timer } from 'rxjs';
...

this.formGroup
  .valueChanges
  .pipe(
    startWith(this.formGroup.value),
    pairWise(),
    debounce(([beforeValueChanges, afterValueChanges]) => 
        beforeValueChanges.search !== afterValueChanges.search 
        ? timer(500) 
        : timer(0)
    )
  ).subscribe(([unusedValue, afterValueChanges]) => {
    // .. Do what you want with afterValueChanges
  }

Explanations for the pipe:

  1. startWith() Get formGroup value, before valueChanges
  2. pairWise() Will create array with [beforeValueChanges, afterValueChanges]
  3. debounce() Check if textInput is different between before and after, so let put a debounce 500 if true.

Thanks to Sasha answer

Paregoric answered 21/3, 2023 at 13:41 Comment(0)
I
0

There's an issue on GitHub, which may(?!) solve this in the future:

https://github.com/angular/angular/issues/19705

Then it should be possible (like in AngularJS) to debounce single input fields.

Insecurity answered 22/3, 2018 at 10:40 Comment(0)
G
0

I faced the same issue and in my case creating a new Input component and providing debounce value in html solved issue without any custom code

Gratiana answered 6/4, 2022 at 22:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.