Search code examples
angularangular-forms

Angular forms: value changes for each individual control


I'd like to listen to valuechanges of my form, but not for the entire form but only for the formcontrol that was changed.

If for example my form looks like this.

this.form = this._fb.group({
  firstName: [''],
  lastName: [''],
  ... // other stuff.
});

If I then subscribe to valuechanges

this.form.valueChanges.subscribe((e) => {
  console.log(e);
});

Then filling in a firstname in the form would result in a printout of the entire form value object.

{firstName: 'input', lastName: '', ...}

But what I want to know is which form control (in this case the firstName) was altered without subscribing to each individual form control. Such that my desired output is only

{firstName: 'input'}


Solution

  • Firstly, multiple AbstractControls(e.g FormControl, FormGroup, FormArray) are stored internally as a tree.

    // FG - FormGroup
    // FA - FormArray
    // FC - FormControl
    
        FG
      /   \
    FC    FG
        /    \
      FC     FA
            / | \
          FC FC FC
    

    The above snippet is excerpted from A thorough exploration of Angular Forms.

    Because of that reason, a FormGroup.valueChanges emits when a FormGroup child's valueChanges emits:

    updateValueAndValidity (/* ... */) {
      /* ... */
    
      if (opts.emitEvent !== false) {
        (this.valueChanges as EventEmitter<any>).emit(this.value);
        (this.statusChanges as EventEmitter<string>).emit(this.status);
      }
      
      if (this._parent && !opts.onlySelf) {
        this._parent.updateValueAndValidity(opts);
      }
      
      /* ... */
    }
    

    In the above snippet, if we were to consider your example, _parent could refer to the this.form FormGroup instance when the firstName FormControl receives some input.


    So, a way to achieve what you're looking for would be this:

    merge(
      ...Object.keys(this.form.controls).map(
        k => this.form.controls[k].valueChanges.pipe(
          // Getting a decent format: `{ formControlName: formControlValue }`
          map(v => ({ [k]: v })),
        )
      )
    ).subscribe(console.log)
    

    The FormGroup.controls has this signature:

    public controls: {[key: string]: AbstractControl},