Search code examples
angularangular-reactive-forms

Angular - Reactive forms: right way to have FormGroup in FormArray


What is the right way to create a FormArray of FormGroup in Angular?

I have the following:

component.ts:

export class DrawerContentComponent implements OnInit {
  consumption = new FormGroup({
    electricity: new FormControl(0),
    water: new FormControl(0),
    gas: new FormControl(0),
  });

  consumptionPerMonth = new FormArray([]);

  ngOnInit() {
    for (let index = 0; index < 12; index++) {
      this.consumptionPerMonth.push(this.consumption);
    }
  }

  showYearlyConsumption() {
    console.log(this.consumptionPerMonth.value);
  }
}

component.html:

<table>
  <tr>
    <th>Monat</th>
    <th class="w-40">Stromverbrauch in kWh</th>
    <th class="w-40">Wasserverbrauch in m&#179;</th>
    <th class="w-40">Gasverbrauch in kWh</th>
  </tr>

  <tr *ngFor="let month of consumptionPerMonth.controls; let i = index">
    <ng-container [formGroup]="month">
      <td>{{ i }}</td>
      <td>
        <input
          class="border"
          type="number"
          [formControl]="month.controls.electricity"
        />
      </td>
      <td>
        <input
          class="border"
          type="number"
          [formControl]="month.controls.water"
        />
      </td>
      <td>
        <input
          class="border"
          type="number"
          [formControl]="month.controls.gas"
        />
      </td>
    </ng-container>
  </tr>
</table>
<button (click)="showYearlyConsumption()">show</button>

When I fill for example on the website the first column of first row with 1, like this:

enter image description here

and click then on the "show" button then I have in console.log(this.consumptionPerMonth.value) on all 12 items the value of "electricity" = 1

like this:

console.log(this.consumptionPerMonth.value):

(12) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
0
: 
{electricity: 1, water: 0, gas: 0}
1
: 
{electricity: 1, water: 0, gas: 0}
2
: 
{electricity: 1, water: 0, gas: 0}
3
: 
{electricity: 1, water: 0, gas: 0}
4
: 
{electricity: 1, water: 0, gas: 0}
5
: 
{electricity: 1, water: 0, gas: 0}
6
: 
{electricity: 1, water: 0, gas: 0}
7
: 
{electricity: 1, water: 0, gas: 0}
8
: 
{electricity: 1, water: 0, gas: 0}
9
: 
{electricity: 1, water: 0, gas: 0}
10
: 
{electricity: 1, water: 0, gas: 0}
11
: 
{electricity: 1, water: 0, gas: 0}
length
: 
12
[[Prototype]]
: 
Array(0)

What is the right way to do this, that I have in the "this.consumptionPerMonth.value" only in the first item a 1 for "electricity"?


Solution

  • You are passing the same instance of consumption to the form array and end up actually with same control instance in the array. As of Angular 14 you can strongly type your forms so you can create an interface to describe the consumption like this:

    interface Consumption {
      electricity:  FormControl<number | null>;
      water: FormControl<number | null>;
      gas: FormControl<number | null>;
    }
    

    Then initialize your consumptionPerMonth field like this:

    consumptionPerMonth = new FormArray<FormGroup<Consumption>>([]);
    

    Finally in onInit push a new instance of form group like this:

    ngOnInit() {
      for (let index = 0; index < 12; index++) {
        this.consumptionPerMonth.push(new FormGroup({
          electricity: new FormControl(0),
          water: new FormControl(0),
          gas: new FormControl(0),
        }));
      }
    }
    

    This should solve your issue and you will have strongly typed form which is much easier to work with.