问题
I'm attempting to calculate a total or other value based on a particular value in a FormArray.
I'm finding that when subscribing to valueChanges
and attempting to pass the whole array to something like reduce
, the "new value" isn't present on the parent FormGroup.
Original Example on StackBlitz
Sample:
this.frm.get('arr')['controls'][i].valueChanges
.subscribe((newVal) => {
this.frm.get('total').patchValue(
this.frm.get('arr').value.reduce((acc, curr) => {
return acc + curr.v;
}, 0)
)
});
If arr
has values [0, 1, 2, 3, 4]
and I change the first value to 1
, I still get 10
instead of 11
.
this.frm.get('arr').value
shows all the initial values and not the updated values.
I'm fairly new to Reactive Forms so I'm sure I'm just missing something fundamental here.
UPDATE (Have a solution, but I'm still lacking understanding):
I'd really love to know why I'm not able to subscribe to changes to the whole array like Eliseo had suggested.
I'm also interested in why there is such a difference between where I use .value
- If the whole point of Reactive Forms are to be Model Driven and then pass frm.value
to your Submit function, then why is .value
so inconsistent throughout the form hierarchy?
UPDATE 2
Updated StackBlitz Solution with guidance from Eliseo after his update
I've found a solution that I feel pretty good about. It doesn't use controls
anywhere and feels very clean. Eliseo's suggested approach pushed me to try this out. I believe the important thing is casting to a FormArray
and executing any push
on that. This keeps the parent
field tied to the rest of your FormGroup
or FormArray
thus it stays included in the valueChanges
event.
I now believe using controls
is an anti-pattern to Angular Reactive Forms, but I need to search for more guidance on that topic.
回答1:
why not listen changes of all array and make total with newValues?
//NOT ['controls'][i]
this.frm.get('arr')['controls'].valueChanges
.subscribe((newVal) => {
this.frm.get('total').patchValue(
//use newVal
newVal.reduce((acc, curr) => {
return acc + curr.v;
}, 0)
)
});
That's not work, Change the code of buildForm for this:
buildForm() {
let controls:any[]=[];
for (let i:number = 0; i < 5; ++i) {
controls.push(this.builder.group({
v: i
}))
this.frm = this.builder.group({
total: [{value: '', disabled: true}],
arr: this.builder.array(controls)
});
this.frm.get('arr').valueChanges
.subscribe((newVal) => {
let total:number=0;
newVal.forEach(e=>{
total=total+parseInt(e.v)});
this.frm.get('total').patchValue(total,0)
});
}
}
The main changes is use forEach and not reduce, the use of parseInt, and the way to make the formArray.
回答2:
I got this to work.
this.frm.get('arr')['controls'][i].valueChanges
.subscribe((newVal) => {
this.frm.get('total').patchValue(
this.frm.get('arr').value.reduce((acc, curr) => {
return acc + curr.v;
}, 0) + (+newVal.v)
)
});
Something about your form is a little strange. All 5 controls bind to the same formControlName
"v". I think that may be the reason this form isn't acting the way you would like. The above does work, but adding the newVal
on the end like this just seems odd.
UPDATE:
This is working for all field changes.
this.frm.get('arr')['controls'][i].valueChanges
.subscribe((newVal) => {
const arr = <FormArray>this.frm.controls['arr'];
let total = 0;
arr.controls.forEach((c: any) => {
console.log(c.value);
total+= +c.value.v;
});
console.log('total',total);
this.frm.get('total').patchValue(total)
});
Not the prettiest code I have ever seen, but it works. Get rid of the console logs, and it may be something you can live with. :)
来源:https://stackoverflow.com/questions/49019672/calculated-values-using-angular-formarray