Dinesh Kumar
Dinesh Kumar

Reputation: 503

Angular 15 Create Dynamic formArray inside array using reactive forms

I am trying to create a Dynamic formArray inside the array, but I am getting an error

Property 'controls' does not exist on type 'AbstractControl<any, any>

I create a project on Stackblitz

https://stackblitz.com/edit/reactive-form-validation-angular-15-klcsph

I just want to resolve the error

Upvotes: 1

Views: 134

Answers (2)

Eliseo
Eliseo

Reputation: 58099

Another approach: I like only use a "getter" for the formArrays (the inner it's not a getter because we need the "index" of the row, but I hope the idea was clear)

  get grid(): FormArray {
    return this.myForm.get('grid') as FormArray;
  }
  getRow(index:number)
  {
    return this.grid.at(index) as FormArray
  }

<form [formGroup]="myForm">
  <div formArrayName="grid">
    <div *ngFor="let row of grid.controls; let i = index" [formArrayName]="i" >
      <div *ngFor="let cell of getRow(i).controls; let j = index">
        <input [formControlName]="j"/>
      </div>
      <button type="button" (click)="removeRow(i)">Remove Row</button>
    </div>
  </div>
  <button type="button" (click)="addRow()">Add Row</button>
  <button type="button" (click)="addColumn()">Add Column</button>
</form>

BTW, the function addRow should becomes like

  addRow() {
    const cells=this.grid.controls.length?
                            this.getRow(0).controls.map(_=>''):
                            []
    this.grid.push(this.fb.array(cells));
  }

In case we can only mannagge the "grid", (that's, the grid don't belong to any formGroup) we can use another "getter" for the formControls

  controls(row:number,col:number)
  {
    return this.grid.get(row+'.'+col) as FormControl
  }

In this case simply (see that the're no FormGroup)

  <div *ngFor="let row of grid.controls; let i = index"  >
    <div
      *ngFor="let cell of getRow2(i).controls; let j = index"
    >
      <input [formControl]="controls(i,j)"/>
    </div>
    <button type="button" (click)="grid2.removeAt(i)">Remove Row</button>
  </div>

A stackblitz with the two approaches

Upvotes: 0

Naren Murali
Naren Murali

Reputation: 58492

Below are the changes made to make it work.

  • Typescript was giving multiple errors for the types being wrong, so I used as FormArray or <FormArray> to give typing to the controls and formArrays.

code

  // Getters for easy access to FormArrays
  get gridControls(): FormArray[] {
    return (<FormArray>this.myForm.get('grid')).controls as FormArray[];
  }

  // Getters for easy access to FormArrays
  get grid(): FormArray {
    return this.myForm.get('grid') as FormArray;
  }

  getFormArrayControls(input: FormArray): FormControl[] {
    return input.controls as FormControl[];
  }
  • Then I changed the direct access of controls grid.controls to getGridControls to ensure that the right types were sent to the *ngFor this also got rid of many errors.

html

...
<div *ngFor="let row of gridControls; let i = index" [formGroupName]="i">
  <div
    *ngFor="let cell of getFormArrayControls(row); let j = index"
    [formGroupName]="j"
  >
    <input [formControl]="cell" />
  </div>
  <button type="button" (click)="removeRow(i)">Remove Row</button>
</div>
...

Full code

ts

import { Component, OnInit } from '@angular/core';
import {
  FormBuilder,
  FormGroup,
  FormArray,
  Validators,
  FormControl,
} from '@angular/forms';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  myForm: FormGroup;

  constructor(private fb: FormBuilder) {}

  ngOnInit() {
    this.myForm = this.fb.group({
      grid: this.fb.array([]),
    });

    this.addRow();
    this.addColumn();
  }

  // Getters for easy access to FormArrays
  get gridControls(): FormArray[] {
    return (<FormArray>this.myForm.get('grid')).controls as FormArray[];
  }

  // Getters for easy access to FormArrays
  get grid(): FormArray {
    return this.myForm.get('grid') as FormArray;
  }

  getFormArrayControls(input: FormArray): FormControl[] {
    return input.controls as FormControl[];
  }

  // Add a new row to the grid
  addRow() {
    const newRow = this.fb.array([]);
    this.grid.push(newRow);
  }

  // Add a new column to each row
  addColumn() {
    this.grid.controls.forEach((row: FormArray) => {
      row.push(this.fb.control('', Validators.required));
    });
  }

  // Remove a row from the grid
  removeRow(rowIndex: number) {
    this.grid.removeAt(rowIndex);
  }

  // Remove a column from each row
  removeColumn(columnIndex: number) {
    this.grid.controls.forEach((row: any) => {
      row.removeAt(columnIndex);
    });
  }
}

html

<form [formGroup]="myForm">
  <div formArrayName="grid">
    <div *ngFor="let row of gridControls; let i = index" [formGroupName]="i">
      <div
        *ngFor="let cell of getFormArrayControls(row); let j = index"
        [formGroupName]="j"
      >
        <input [formControl]="cell" />
      </div>
      <button type="button" (click)="removeRow(i)">Remove Row</button>
    </div>
  </div>
  <button type="button" (click)="addRow()">Add Row</button>
  <button type="button" (click)="addColumn()">Add Column</button>
</form>

{{ myForm.value | json }}

stackblitz

Upvotes: 1

Related Questions