I have a reactive form with over 10 form controls and using subscription on valueChanges observable to detect changes. It works perfectly but output is always the entire form value object(meaning all the form controls and their values). Is there a way to simply get the form control name of the field that changed?
this.form = this.fb.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['', Validators.required],
field4: ['', Validators.required],
field5: ['', Validators.required],
field6: ['', Validators.required],
field7: ['', Validators.required],
field8: ['', Validators.required],
field9: ['', Validators.required],
field10: ['', Validators.required],
field11: ['', Validators.required],
field12: ['', Validators.required],
field13: [{ value: '', disabled: true }, Validators.required]
});
this.form.valueChanges.subscribe(
result => this.calculateParams(result)
);
calculateParams(result) {
console.log(result); // giving the entire form.value object
}
I have a reactive form with over 10 form controls and using subscription on valueChanges observable to detect changes. It works perfectly but output is always the entire form value object(meaning all the form controls and their values). Is there a way to simply get the form control name of the field that changed?
this.form = this.fb.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['', Validators.required],
field4: ['', Validators.required],
field5: ['', Validators.required],
field6: ['', Validators.required],
field7: ['', Validators.required],
field8: ['', Validators.required],
field9: ['', Validators.required],
field10: ['', Validators.required],
field11: ['', Validators.required],
field12: ['', Validators.required],
field13: [{ value: '', disabled: true }, Validators.required]
});
this.form.valueChanges.subscribe(
result => this.calculateParams(result)
);
calculateParams(result) {
console.log(result); // giving the entire form.value object
}
Share
Improve this question
asked Jun 24, 2019 at 2:35
dimitridimitri
751 gold badge1 silver badge5 bronze badges
0
7 Answers
Reset to default 5The rxjs way of Eliseo's answer
this.form.valueChanges.pipe(
startWith(this.form.value),
pairwise(),
map(([oldValues, newValues]) => {
return Object.keys(newValues).find(k => newValues[k] != oldValues[k]);
}),
).subscribe(key => {
console.log( key )
});
It's a work-around but if store the old values you can do some like
this.old={...this.myForm.value}
this.myForm.valueChanges.subscribe(res=>{
const key=Object.keys(res).find(k=>res[k]!=this.old[k])
this.old={...this.myForm.value}
})
You can isolate the formcontrol from the formgroup by using the get
method and the access the valueChanges
method on it.
this.form.get('formcontrolName').valueChanges()
.
Note: form.get
returns you the AbstractControl
, which also has the valueChanges
method on it.
Haven't fully tested the code but the idea is to pair the controls and their key then listen to valueChanges
on each control simultaneously and return object key instead of value (and of course you can map both value and key to the output)
const fields={
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['', Validators.required],
field4: ['', Validators.required],
field5: ['', Validators.required],
field6: ['', Validators.required],
}
zip(
from(Object.values(fb.group(fields).controls)),
from(Object.keys(fields))
).pipe(mergeMap([control,key])=>control.valueChanges.pipe(mapTo(key)))
You could subscribe to each change individually.
for (const controlProperty in this.myForm.controls) {
if (this.myForm.controls.hasOwnProperty(controlProperty)) {
this.myForm.controls[controlProperty].valueChanges.pipe(untilDestroyed(this)).subscribe(result => {
console.log(controlProperty + " is now ", result);
});
}
}
This just felt more angular-y to me.
Use valueChanges
for each control after adding control dynamically to FormGroup
.
const ct = {key: 'myControl'};
this.myForm.addControl(ct.key, new FormControl('', Validators.required))
this.myForm.controls[ct.key].valueChanges
.subscribe(d => {
// here ct will be available as closure , so that we can access every form control here and do operation accordingly
console.log(d, ct.key);
})
Here ct
object will be available inside subscribe as a closure.
The method from @alex-walker (https://stackoverflow.com/a/64830665/134120) seems to be the best, but it only works well on flat forms, with simple key-value pairs. If you have nested forms, with FormArrays of FormGroups, the key
property in that method will only return the top-level key of the form, which is not very useful if you want e.g. to indicate to the user the particular input field that triggered the event.
So I combined this method with a deep object diff method described here: https://stackoverflow.com/a/50278133/134120
To get something like this:
this.form.valueChanges
.pipe(
startWith(this.form.value),
pairwise(),
map(([oldValues, newValues]) => {
return bdiff(oldValues, newValues);
})
)
.subscribe(keys => {
console.log(keys);
});
bdiff(a: Record<string, any>, b: Record<string, any>): string[] {
return _reduce(
a,
(res, val, key) =>
res.concat(
(_isPlainObject(val) || Array.isArray(val)) && b
? this.bdiff(val, b[key]).map(x => key + (key.trim ? '' : ']') + (x.search(/^\d/) ? '.' : '[') + x)
: !b || val !== b[key]
? [key + (key.trim ? '' : ']')]
: []
),
[]
);
}
Which will return an array of nested keys such as keys = ['fieldgroup2.fieldArray[3].myNestedField']
which you should easily access and set as invalid with form.get(keys[0]).valid
.