Elias
Elias

Reputation: 357

function is called more than once after debounceTime

I have a reactive form with one field and when the user stops typing, I want to print to the console the value. For this purpose I use the debounceTime() when the value in the field is changed. I read several questions about the use of the debounceTime() like How to use debounceTime in an angular component? and Angular and debounce but my code prints the latest value more than once then the change is detected and the time in the debounceTime function is elapsed.

The input filed of the form is this:

<input matInput formControlName="name" (keyup)="onInputChange()">

The code in function onInputChange is this:

this.userForm.get("name").valueChanges
          .pipe(
            debounceTime(500)
          )
          .subscribe(res => {
            console.log(res)
          });

It the input is test the result in console is the following image:

enter image description here

What could be wrong?

Upvotes: 3

Views: 1102

Answers (2)

AVJT82
AVJT82

Reputation: 73357

As mentioned in other answer, don't have the keyup handler. Just listen to valueChanges. Since you want to make a http-request, we can use switchMap. Also remember to unsubscribe to valueChanges. We also probably want to filter out if user only types empty spaces. So I suggest the following:

import { Subscription } from 'rxjs';
import { debounceTime, switchMap, filter } from 'rxjs/operators';

// ...

formSub = new Subscription();

ngOnInit() {
  this.formSub = this.userForm.get('name').valueChanges.pipe(
    debounceTime(500),
    filter(value => !!value.trim()),
    switchMap((value) => {
      // replace with your request
      return this.myService.postData(value)
    })
  // replace MyInterface with your model
  ).subscribe((data: MyInterface) => console.log(data))
}


ngOnDestroy() {
  this.formSub.unsubscribe();
}

Then to the issue, when you programatically set the value. We can use emitEvent: false, which does not cause valueChanges to fire:

this.userForm.get('name').setValue('my name', { emitEvent: false })

Upvotes: 1

dev-dan
dev-dan

Reputation: 6283

You are calling a function and subscribing on each key press, you only need to subscribe once and not call the (keyup) function as this will catch any changes to the input.

It looks like the function that is being called is likely leading to the creation of four subscriptions, hence four entries in the console.

<input matInput formControlName="name" (keyup)="onInputChange()">

Should just be

<input matInput formControlName="name">

Move this logic into your ngOnInit() so the subscription is created early into the components life cycle and only once.

You can then make a no reference copy of the name and compare it in the valuesChanged() block to ensure it does not match the original name before posting.

userForm = this.formBuilder.group({
  name: ['']
)};
public originalName = '';

public ngOnInit(): void
{
    this.userService.getUserById(this.userId).subscribe(res => 
    {
      this.originalName = JSON.parse(JSON.stringify(res.name));
      this.userForm.controls['name'].setValue(res.name);

      this.userForm.get("name").valueChanges.pipe(debounceTime(500)).
      subscribe(res => 
      {
        if (res.name !== this.originalName) 
        {
          // logic to post to server
        }
      });
    });
}

Would also recommend a read of how to handle subscriptions when the components is destroyed, as this will not handle its self, there are a lot of resources available.

Upvotes: 4

Related Questions