How to get FormControlName of the field which value changed in Angular reactive forms

10,399

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)))
Share:
10,399
dimitri
Author by

dimitri

Updated on June 28, 2022

Comments

  • dimitri
    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
    dimitri almost 5 years
    right, 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
    Phu Ngo almost 5 years
    you can extract out the config for fb.group so that you can use Object.keys on it and subscribe to each field in a forEach loop
  • KiraAG
    KiraAG almost 5 years
    @dimitri Can you try this this.form.valueChanges().pipe(map(form => form.controls), filter(control => control.dirty)).subscribe();
  • dimitri
    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
    dimitri almost 5 years
    @KiraAG - not getting any results(Cannot read property 'dirty' of undefined). What's the chain of thoughts here?
  • KiraAG
    KiraAG almost 5 years
    The 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
    KiraAG almost 5 years
    Ok Sorry, I misread form.controls returns array, it actually return you an object. Can you try this this.form.valueChanges().pipe(map(form => Object.values(form.controls)), filter(control => control.dirty)).subscribe(). Now you will get an array of form controls.
  • Phu Ngo
    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
    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
    KiraAG almost 5 years
    Isn’t that what you wanted?
  • dimitri
    dimitri almost 5 years
    @KiraAG sorry if question was not clear, but no - just a single field which value was updated last
  • KiraAG
    KiraAG almost 5 years
    Oh , I think this is a bit complex requirement, for which may you need pairwise operator.
  • dimitri
    dimitri almost 5 years
    This seems to do the trick. I originally though of something similar but hoped that there's maybe a "slicker" solution.. Thanks!
  • Eliseo
    Eliseo almost 5 years
    In 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
    Daniel Saner about 3 years
    This 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
    AsGoodAsItGets about 2 years
    Terribly inefficient.