pengz
pengz

Reputation: 2471

Angular reactive forms module array of form group objects binding and validation

I have a reactive form with a somewhat complex structure. I am struggling with creating form controls and validations for an array of form group objects in the form.

Similar questions have been asked on here before, but I wasn't able to find any answers for how to bind to an array of form groups.

The parent form group Record has top-level properties and then a nested array of form group objects Items.

Each Item has the properties "Title" and "Description". The Title and Description need controls and validations. I do not necessarily know how many Items will be required on the form, it is dependent upon how many "Items" each "Record" has when the form is initialized with default values.

Here is a stackblitz: https://stackblitz.com/edit/angular-nkhgsj

Here are some snippets from the code:

Example data:

  // Placeholder to emulate data from an API response
  recordAPIResponse =
    {
      "ID": 12345,
      "Items": [
        {
          "Title": "Example title 1",
          "Description": "Example description 1"
        },
        {
          "Title": "Example title 2",
          "Description": "Example description 2"
        },
        {
          "Title": "Example title 3",
          "Description": "Example description 3"
        }
      ]
    };

The form is initialized with the Record values that are returned from an API response.

  private initRecordForm(record: Record) {

    // Create items controls with validations
    const itemsCtrls = this.createItemsCtrls(record.Items);

    const recordForm = this.fb.group({
      ID: [record.ID, Validators.compose([
        Validators.minLength(3),
        Validators.maxLength(10),
        Validators.pattern(/^[0-9]*$/)
      ])],
      items: itemsCtrls

    });

    return recordForm;

  }

The "Items" controls are pre-populated and then placed into the parent form group when it is initialized.

  // Create a form control for each object in Items array
  private createItemsCtrls(items: Item[]) {

    // Initilize an empty form array
    let formArrayCtrls = this.fb.array([]);

    // Iterate each value
    if (items && items.length > 0) {
      items.forEach(item => {
        // Create a new form control for each array value and push to form array
        formArrayCtrls.push(
          this.fb.group({
            Title: [item.Title, Validators.compose([
              Validators.minLength(5),
              Validators.maxLength(10),
              Validators.pattern(/^[0-9A-Za-z]*$/)
            ])],
            Description: [item.Description, Validators.compose([
              Validators.minLength(1),
              Validators.maxLength(20),
              Validators.pattern(/^[0-9A-Za-z]*$/)
            ])]
          })
        );
      });
    }

    console.log(formArrayCtrls);
    return formArrayCtrls;

  }

This works, the form group is created with the nested array of form group objects.

However, I do not now know to bind the nested array of Form Group objects to the template, capture the latest value and perform the validations.

<form [formGroup]="recordForm" (submit)="onSubmit()">
  <div>
    <label>ID: </label>
    <input formControlName="ID">
  </div>
  <div formArrayName="Items">
    <ng-container *ngFor="let item of record.Items">
      <label>Title: </label>
      <input formControlName="Title">
      <label>Description: </label>
      <input formControlName="Description">
      <br />
    </ng-container>
  </div>
  <button type="submit">Submit</button>
</form>

Error message:

ERROR Error: Cannot find control with path: 'Items -> Title'

Upvotes: 3

Views: 7920

Answers (1)

bryan60
bryan60

Reputation: 29325

So your form array is actually an array of form groups, and each form groups "name" is its index in the array, so you need to let Angular know the index to access it:

<ng-container
  *ngFor="let item of record.Items; let i = index"
  [formGroupName]="i">
</ng-container>

blitz link: https://stackblitz.com/edit/angular-nznpfj?file=src/app/app.component.html

Upvotes: 8

Related Questions