Search code examples
angulartypescriptangular-reactive-formsformarray

Add dynamic fields to the form


I am trying to add 2 dynamic input fields into my form.

<div formArrayName="details">
  <div *ngFor="let detail of _detailRowNumber; index as i">
    <mat-form-field appearance="fill">
      <mat-label>Label of a detail</mat-label>
      <input id="detail-label" matInput type="text" [formControlName]="i">
    </mat-form-field>

    <mat-form-field appearance="fill">
      <mat-label>Description of a detail</mat-label>
      <input id="detail-description" matInput type="text" [formControlName]="i">
    </mat-form-field>

    <button type="button" *ngIf="_detailRowNumber.length > 1" (click)="decreaseDetailRow(detail)" mat-fab color="primary" aria-label="Remove a new row from the detail list">
      <mat-icon>remove</mat-icon>
    </button>

    <button type="button" (click)="increaseDetailRow(i)" mat-fab color="primary" aria-label="Add a new row to the detail list">
      <mat-icon>add</mat-icon>
    </button>
  </div>
</div>
_detailRowNumber: Array<number> = [0];

details: FormGroup = new FormGroup({
  details: new FormArray([
    new FormControl('label'),
    new FormControl('description')
  ])
})

fields: string[] = [];
formMain = this.fb.group({
  details: this.details
});

increaseDetailRow(index: number): void {
  this._detailRowNumber.splice(++index, 0, Date.now());
}

decreaseDetailRow(index: number): void {
  this._detailRowNumber = this._detailRowNumber.filter((item) => item != index);
}

But I get this error:

ERROR Error: Cannot find control with path: 'details -> 0'

This is what I expect:

{
  details: [
    { label: "My label 1", description: "My description 1" },
    { label: "My label 2", description: "My description 2" }
  ]
}

So how can I achieve my goal?


Solution

  • Your details form control should be the FormArray with FormGroup(s), rather than the FormArray with FormControl(s) in order to achieve the expected result as attached.

    <div [formGroup]="formMain">
      <div formArrayName="details">
        <div *ngFor="let detail of _detailRowNumber; index as i">
          <ng-container [formGroupName]="i">
            <mat-form-field appearance="fill">
              <mat-label>Label of a detail</mat-label>
              <input
                id="detail-label"
                matInput
                type="text"
                formControlName="label"
              />
            </mat-form-field>
    
            <mat-form-field appearance="fill">
              <mat-label>Description of a detail</mat-label>
              <input
                id="detail-description"
                matInput
                type="text"
                formControlName="description"
              />
            </mat-form-field>
    
            <button
              type="button"
              *ngIf="_detailRowNumber.length > 1"
              (click)="decreaseDetailRow(detail)"
              mat-fab
              color="primary"
              aria-label="Remove a new row from the detail list"
            >
              <mat-icon>remove</mat-icon>
            </button>
    
            <button
              type="button"
              (click)="increaseDetailRow(i)"
              mat-fab
              color="primary"
              aria-label="Add a new row to the detail list"
            >
              <mat-icon>add</mat-icon>
            </button>
          </ng-container>
        </div>
      </div>
    </div>
    
    ngOnInit() {
      this.formMain = this.fb.group({ details: this.fb.array([]) });
      this.addDetailsFormGroup();
    }
    
    addDetailsFormGroup() {
      (this.formMain.controls.details as FormArray).push(
        new FormGroup({
          label: new FormControl('label'),
          description: new FormControl('description'),
        })
      );
    }
    
    increaseDetailRow(index: number): void {
      this._detailRowNumber.splice(++index, 0, Date.now());
      this.addDetailsFormGroup();
    }
    
    decreaseDetailRow(index: number): void {
      this._detailRowNumber = this._detailRowNumber.filter(
        (item) => item != index
      );
    
      (this.formMain.controls.details as FormArray).removeAt(index);
    }
    

    Demo @ StackBlitz