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

40 views Asked by At

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"?

2

There are 2 answers

0
Ricudo On

I see the only one problem in your code. You push the same instance of FormGroup to your FormArray but you need differen form groups, so the simplest way to solve the problem just create new FormGroup inside your loop. Also to better readability you can move it to a separate function so the result will be next:

export class DrawerContentComponent implements OnInit {
  consumptionPerMonth = new FormArray([]);

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

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

  private _createConsumptionFormGroup(): FormGroup {
      return new FormGroup({
        electricity: new FormControl(0),
        water: new FormControl(0),
        gas: new FormControl(0),
      });
    }
}
1
wnvko On

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.