In Angular
, is there a way to identify which FormGroup
/FormControl
in a dynamicFormArray
emitted the valueChanges
event?
My FormArray
is dynamic. It starts out empty and users could add a FormGroup
to the FormArray
by clicking a button.
When valueChanges, I need to re-validate the control. Since I dont know which control emitted the event, I loop through the entire FormArray
and validate all FormGroup
/FormControl
even though only one control changed - and this is every time when anything in the array changes. How can I avoid doing this?
this.myFormArray .valueChanges .subscribe(data => this.onValueChanged(data)); onValueChanged(data?: any): void { // the data I receive is an entire form array. // how can I tell which particular item emitted the event, // so I don’t need to loop through entire array and run validation for all items. for (let control in this.myFormArray.controls) { // run validation on each control. } }
The ValueChanges is an event raised by the Angular forms whenever the value of the FormControl, FormGroup or FormArray changes. It returns an observable so that you can subscribe to it. The observable gets the latest value of the control. It allows us to track changes made to the value in real-time and respond to it.
You can manually clear each FormArray element by calling the removeAt(i) function in a loop. The advantage to this approach is that any subscriptions on your formArray , such as that registered with formArray. valueChanges , will not be lost.
First, we need to import the FormArray from the Angular Forms Module. Build a formGroup orderForm using the FormBuilder. We define items as FormArray. We need to capture two fields under each item, the name of the item & description and price.
To build on epsilon's answer, which collects an array of valueChanges observables and merges them - you can also pipe the value changes thru a pipe, which adds necessary context to the changes stream via map.
merge(...this.formArray.controls.map((control: AbstractControl, index: number) => control.valueChanges.pipe(map(value => ({ rowIndex: index, value }))))) .subscribe(changes => { console.log(changes); });
Output:
{ rowIndex: 0 value: { <current value of the changed object> } }
Note that the first call to map (on controls) is on an array. The second map (in the pipe) is an RxJs map. I really like this website to help get these operators straight and imagine what these streams of events look like: https://rxmarbles.com/#map (EDIT: I now think this post is far better: https://indepth.dev/learn-to-combine-rxjs-sequences-with-super-intuitive-interactive-diagrams/)
EDIT: Because I was watching a FormArray that could be modified by the user via the add/delete buttons, I added a changesUnsubscribe subject and reference that in the takeUntil. This allows me to discard the old set of watches and setup new ones when the list changes. So now I call watchForChanges() when items are added or removed from the list.
changesUnsubscribe = new Subject(); ... watchForChanges() { // cleanup any prior subscriptions before re-establishing new ones this.changesUnsubscribe.next(); merge(...this.formArray.controls.map((control: AbstractControl, index: number) => control.valueChanges.pipe( takeUntil(this.changesUnsubscribe), map(value => ({ rowIndex: index, control: control, data: value }))) )).subscribe(changes => { this.onValueChanged(changes); }); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With