I am using Angular Reactive Forms to iterate through an array of values I would like to have a total field after the Form Array that updates on changes of the Form Array control values.
Sample data:
primaryData = {
products: [
{
name: 'test-product',
unmoved: 21,
moved: 18
},
{
name: 'another-product',
unmoved: 18,
moved: 42
}
]
}
I am creating the Reactive Form Controls and Array as follows:
setPrimaryQuantities() {
const control = <FormArray>this.primaryForm.controls.quantities;
this.primaryData.products.forEach(product =>
control.push(this.fb.group({
name: [product.name, Validators.required],
unmoved: [product.unmoved, Validators.required],
moved: [product.moved, Validators.required]
}))
)
}
ngOnInit() {
this.primaryForm = this.fb.group({
quantities: this.fb.array([]),
unmovedTotal: '',
movedTotal: ''
})
this.setPrimaryQuantities();
}
What is the best way to have my unmovedTotal
and movedTotal
Controls update based on the changes in the Array controls. Here's a StackBlitz demonstrating my structure.
The totals you're looking for shouldn't be in the form in my opinion. It's disabled without any condition in your Typescript and I can't think of a use case where you'd edit the totals directly. It's just a computed value out of the form.
That said, a nice thing would be to have an observable of type:
total$: Observable<{ moved: number, unmoved: number }>
To do that, as you've used a reactive form, it's pretty straight forward :)
this.total$ = this.primaryForm.valueChanges.pipe(
startWith(this.primaryForm.value),
map(f => f.quantities.reduce(
(acc, q) =>
({
moved: acc.moved + q.moved,
unmoved: acc.unmoved + q.unmoved
}),
{ moved: 0, unmoved: 0 }
))
);
Every time the form changes, you want to get the new totals. When you want to produce a new value (totals) out of an initial one (values of the form), use the map
operator.
On the form, we want to loop over the quantities and produce a sum for both moved
and unmoved
properties. So here we cannot use map
(the basic JS map, not the one from RXJS) because it'd produce an array. For that, we can use reduce
(once again the one from pure JS, no RXJS here).
Now that we've got our observable, we just need to use it into the view. In order to avoid two subscriptions, we can use ngIf
with the as
syntax:
<tr *ngIf="total$ | async as total">
<td>Totals</td>
<td>{{ total.unmoved }}</td>
<td>{{ total.moved }}</td>
</tr>
Here's a complete example based on your initial Stackblitz example:
https://stackblitz.com/edit/angular-kbk3zm?file=src%2Fapp%2Fapp.component.ts