How to get FormControlName of the field which value changed in Angular reactive forms
Solution 1
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}
})
Solution 2
The 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 )
});
Solution 3
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.
Solution 4
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)))
dimitri
Updated on June 28, 2022Comments
-
dimitri almost 2 years
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 }
-
dimitri almost 5 yearsright, but then in order to subscribe to all values I would have to subscribe to each of them separately(for each form control) and I want to avoid that because I'll have forms with a lot more fields than this.
-
Phu Ngo almost 5 yearsyou can extract out the config for
fb.group
so that you can useObject.keys
on it and subscribe to each field in a forEach loop -
KiraAG almost 5 years@dimitri Can you try this
this.form.valueChanges().pipe(map(form => form.controls), filter(control => control.dirty)).subscribe()
; -
dimitri almost 5 years@PhuNgo wouldn't that many subscriptions(e.g. 50+) on a single component at some point be a memory bottleneck?
-
dimitri almost 5 years@KiraAG - not getting any results(
Cannot read property 'dirty' of undefined
). What's the chain of thoughts here? -
KiraAG almost 5 yearsThe idea is to get the form controls and filter them using the property
dirty
, which filters out the controls whose values has been changed in the UI. -
KiraAG almost 5 yearsOk Sorry, I misread
form.controls
returns array, it actually return you an object. Can you try thisthis.form.valueChanges().pipe(map(form => Object.values(form.controls)), filter(control => control.dirty)).subscribe()
. Now you will get an array ofform controls
. -
Phu Ngo almost 5 years@dimitri subscribe to each field in the forEach loop, not to the whole group. But you made a good point with the bottleneck issue
-
dimitri almost 5 years@KiraAG - but wouldn't that just return all "dirty" values, meaning all the fields that have been "touched"? What if I decide to switch back between fields and continue changing them?
-
KiraAG almost 5 yearsIsn’t that what you wanted?
-
dimitri almost 5 years@KiraAG sorry if question was not clear, but no - just a single field which value was updated last
-
KiraAG almost 5 yearsOh , I think this is a bit complex requirement, for which may you need pairwise operator.
-
dimitri almost 5 yearsThis seems to do the trick. I originally though of something similar but hoped that there's maybe a "slicker" solution.. Thanks!
-
Eliseo almost 5 yearsIn Angular 6 and Angular 7, there was a different between myform.get('...').value and "res", or between res and myform.value[...], (even I think remember that you could subscribe to (res,old)=>{..}) but in Angular 8 looks like there's no diference. Really I don't know if ther'e a better solution
-
Daniel Saner about 3 yearsThis seems like a nice solution. However, the first time this is triggered, despite the
startWith
, it will always return the first control of the form for me, regardless of which one actually had the change. Second time onwards, it works fine. I'm confused because even the first time, if I compare oldValues and newValues, the only difference is in the control that actually changed its value. -
AsGoodAsItGets about 2 yearsTerribly inefficient.