Search code examples
rxjsobservableangular8angular-piperxjs-observables

angular subscribe to changes only if previous value of the form control !== currentvalue of the formcontrol


I would like to call onDataChange() only if previous value of input / selection != current Value of input / selection.

However I've noticed that it is still calling onDataChange() whenever put the cursor on the input field (but didn't change any value of the input field / change selection of the dropdown value).

I have used startWith(null), pairwise, and then filter(prev!=next) but it still executing the onDataChange() even though the previous value is same as next value.

The main problem is when putting the cursor on the input field but doesn't change any value, it still call onDataChange().

I would like to call onDataChange() only if there is changes in the input value/ changes in the dropdown selection. onDataChange() will invoke the api call, which might takes longer time to retrieve the result.

Does anybody experience similar issue and could provide guidance? Thanks.

'

  <form class="form" [formGroup]="numberForm" noValidate [style.width.px]="width">
       <select name="types" id="dropdown-number-types" formControlName="dropdown">
           <option *ngFor="let type of types" [value]="type">{{type}}</option>
       </select>
    
       <input id="onValue" type="text" name="onValue" formControlName="onValue">
       <select name="equalsUnit" id="dropdown-number-unit" formControlName="onValueUnit">
            <option *ngFor="let unit of units" [value]="unit">{{unit}}</option>
       </select>
</form>

ngOnInit() {
  const valueChanges: Observable<any>[] = [
    this.numberForm.get('dropdown').valueChanges,
    this.numberForm.get('onValue').valueChanges,
    this.numberForm.get('onValueUnit').valueChanges
  ].map(obs =>
      obs.pipe(
          startWith(null), pairwise(),
          filter(([prev, next]) => prev !== next),
      )
  );

  // Merge all valueChanges observables into one - and debounce time for 250ms to avoid processing
  // events in quick succession which can happen if user is changing values quite fast in UI.
  merge(...valueChanges)
  .pipe(
      takeUntil(this.ngUnsubscribe),
      debounceTime(this.delay)
  )
  .subscribe(([prev, next]: [any, any]) => {
      this.onDataChange();
  });
  this.updateValidators();
  super.ngOnInit();
}

if I remove the merge and just watch for changes for formcontrolname of 'onValue', then it will execute onDataChange() only when there is difference in the value.

Is there any alternative that I can use for merging those 3 formcontrolname and watch for changes for any of the formcontrolname?


Solution

  • If understand correctly you want the distinctUntilChanged operator and this is how I would refactor your code:

    const valueChanges: Observable<any>[] = [
      this.numberForm.get('dropdown').valueChanges,
      this.numberForm.get('onValue').valueChanges,
      this.numberForm.get('onValueUnit').valueChanges
    ].map(obs => obs.pipe(startWith(null)));
    
    combineLatest(valueChanges).pipe(
      debounceTime(this.delay),
      distinctUntilChanged((prev, curr) =>
        prev[0] === curr[0] && prev[1] === curr[1] && prev[2] === curr[2]
      ),
      takeUntil(this.ngUnsubscribe),
    ).subscribe(() => {
      this.onDataChange();
    });
    

    Note:

    • I am using combineLatest, because merge will emit 3 seperate events and combineLatest will emit 1 combined event at the beginning. Afterwards combineLatest will emit on every change and will give you the updated combined value of 3 Observables, where merge would give you only one updated value not the combined.
    • use distinctUntilChanged to compare new combined value to the previous combined value. I specifically put it after the debounceTime, because you need to compare new debounced value to the old debounced value.