Wouter Vandenputte
Wouter Vandenputte

Reputation: 526

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'}

Upvotes: 6

Views: 10155

Answers (3)

Andrei Gătej
Andrei Gătej

Reputation: 11934

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},

Upvotes: 5

Vibin Thomas
Vibin Thomas

Reputation: 71

There are multiple ways to listen form control changes:

custom validation method:

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

fieldValidator(control: FormControl) {
     // control?.value available here
     // return null
}

or cross-validation method:

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

export const exampleValidator: ValidatorFn = (control: AbstractControl): ValidationErrors | null => {
  const firstName = control.get('firstName');
  // firstName?.value available here, etc
  // return condition ? null : { inValid: true};
};

Upvotes: 0

Souvanik Dev
Souvanik Dev

Reputation: 101

You can try something like this

  this.form.get("firstName").valueChanges.subscribe(selectedValue => {
      console.log(selectedValue)     
    })
     

Upvotes: 3

Related Questions