Reputation: 35
In a form group that is dynamically generated with different FormControl names, valueChanges() is emitting the whole array. How we can identify the exact FormControl name which got updated?
Upvotes: 3
Views: 1566
Reputation: 21309
There is no built-in way. But you can use a helper as follows:
import { DestroyRef, inject } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms';
import { merge, of, tap } from 'rxjs';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
/**
* Generic Type to extract the value of FormGroup
* */
type FormGroupValue<T extends FormGroup> = ReturnType<T['value']>;
export class ControlHelper {
/**
* Constructor for ControlHelper.
* @param destroyRef - Reference for managing subscriptions to avoid memory leaks.
* Note: To prevent memory leaks, avoid using a destroyRef of a root service.
* If the control observer is created within a component, a directive, or a service provided by a component/directive, it's fine.
*/
constructor(private destroyRef: DestroyRef = inject(DestroyRef)) {}
/**
* Watches the control's value, emitting the current value immediately and then on subsequent changes.
* @param control - The form control to watch.
* @param callback - The function to call with the control's value and the control itself.
*/
watch<C extends AbstractControl<any>>(
control: C,
callback: (value: C['value'], control: C) => void
) {
this.observeControl(control, callback, true);
}
/**
* Listens for changes to the control's value, emitting only on changes (not the initial value).
* @param control - The form control to observe.
* @param callback - The function to call with the control's value and the control itself.
*/
onChange<C extends AbstractControl<any>>(
control: C,
callback: (value: C['value'], control: C) => void
) {
this.observeControl(control, callback, false);
}
/**
* Add a callback on a formGroupChange. The callback provides the value, the formgroup itself and an array of the controls which changed
*/
onFormGroupChange<FG extends FormGroup>(
formGroup: FG,
callback: (
value: FormGroupValue<FG>,
formGroup: FG,
changedControls: Array<AbstractControl<any>>
) => void
) {
let changedControls: Array<AbstractControl> = [];
const directChildren = Object.values(formGroup.controls);
directChildren.forEach((control: AbstractControl<any>) =>
this.onChange(control, (_, control) =>
changedControls.push(control)
)
);
this.onChange(formGroup, (value) => {
callback(value, formGroup, changedControls);
changedControls = [];
});
}
/**
* Observes changes to the control's value and optionally emits the current value immediately.
* @param control - The form control to observe.
* @param callback - The function to call with the control's value and the control itself.
* @param emitCurrentValue - Whether to emit the current value immediately.
*/
private observeControl<C extends AbstractControl<any>>(
control: C,
callback: (value: C['value'], control: C) => void,
emitCurrentValue: boolean
) {
const observables = emitCurrentValue
? [control.valueChanges, of(control.value)]
: [control.valueChanges];
merge(...observables)
.pipe(
takeUntilDestroyed(this.destroyRef),
tap((values) => callback(values, control))
)
.subscribe();
}
}
Note: To prevent memory leaks, the helper uses the DestroyRef. You must therefore create the helper in the constructor (or initialize it directly in the class scope) or provide your DestroyRef instance manually.
onFormGroupChange
is the method you need to register a callback on a formGroup. The callback will provide the changed value of the formGroup, the formGroup AND an array of the controls (yes you can have many) which triggered the valueChange.
Upvotes: 0
Reputation: 8184
You can listen to the FormControl itself like this
formGroup.controls.forEach(element => {
formGroup.get(element).valueChanges = onControlValueChange;
});
onControlValueChange(v){
// doTheJobHere();
}
You can also follow the same logic for FormArray
formArray.forEach(item => {
(formArray[item].controls||[]).forEach(element => {
formArray[item].get(element).valueChanges = onControlValueChange;
});
});
Upvotes: 1
Reputation: 5267
I don't think there is default out of the box solution from Angular for this.
You'll have to do this kind of manually, after initiating the form, save a copy of form.value
in a temp variable, then on your .valueChnages()
call compare the new value with the temp value and extract the key
for each that doesn't match.
Another way to do it without using a temp variable is to check for which fields got dirty as suggested here:
Angular Reactive forms : how to get just changed values
But the problem with is that sometime fields get dirty but no value changes. user may type something but then change their mind and delete that and leave it empty.
Upvotes: 0