Angular 2 Accessing Nested FormArrays using FormBu

2019-02-03 17:03发布

问题:

First of all I just begin with Angular 2 and I'm trying to build a nested form and validate it.

Here's part of my ts file:

ngOnInit() {
  this.myForm = this.formBuilder.group({
    projects: this.formBuilder.array([
      this.initProjects()
    ])
  });
}

initProjects(): any {
  return this.formBuilder.group({
    name: ['', [Validators.required, Validators.minLength(3)]],
    some_array: this.formBuilder.array([
      this.formBuilder.group({
        name: ['', Validators.required],
        attr: ['', Validators.required],
        some_id: [1, Validators.required]
      })
    ])
  });
}

addProject(): void {
  const control = < FormArray > this.myForm.controls['projects'];
  control.push(this.initProjects());
}

View:

<form [formGroup]="myForm" novalidate (ngSubmit)="onSubmit(myForm)">
  <div formArrayName="projects">
    <div *ngFor="let project of myForm.controls.projects.controls; let i = index">
      <div [formGroupName]="i">
        <md-input placeholder="Name" formControlName="name"></md-input>
      </div>
      <div *ngFor="let some_obj of project.controls.some_array.controls; let x = index">
        <div [formGroupName]="x">
          <div>
            <md-input placeholder="Nome" formControlName="controls.some_array.controls.name"></md-input>
            <small *ngIf="!some_obj.controls.name.valid">
                    Nome é requerido
                  </small>
          </div>
          <md-input type="number" placeholder="Cost" formControlName="controls.some_array.controls.attr" required></md-input>
        </div>
      </div>
    </div>
  </div>
  <button type="submit" md-raised-button color="primary" [disabled]="!myForm.valid">Submit</button>
</form>
<pre>form value: <br>{{myForm.value | json}}</pre>

The output of form value:

form value: 
{
  "projects": [
    {
      "name": "",
      "some_array": [
        {
          "name": "",
          "attr": "",
          "some_id": 1
        }
      ]
    },
    {
      "name": "",
      "some_array": [
        {
          "name": "",
          "attr": "",
          "some_id": 1
        }
      ]
    }
  ]
}

Well, as you can see I have some arrays called projects, with 1 array inside each one.

So the problem is that I'm not able to validate each control of some_array array.

Actually I'm getting the following error:

ORIGINAL EXCEPTION: Cannot find control with path: 'projects -> 0 -> controls.some_array.controls.name PS: I already tried to put it in a div, as below:

But I also got an error:

Cannot find control with path: 'projects -> some_array' Thanks in advance. Any help would be appreciated.

回答1:

Try the following HTML:

<form [formGroup]="myForm" novalidate (ngSubmit)="onSubmit(myForm)">
    <div formArrayName="projects">
        <div [formGroupName]="i" *ngFor="let project of myForm.controls.projects.controls; let i = index">
            <md-input placeholder="Name" formControlName="name"></md-input>
            <div formArrayName="some_array">
                <div [formGroupName]="x" *ngFor="let some_obj of project.controls.some_array.controls; let x = index">
                    <div>
                        <md-input placeholder="Nome" formControlName="name"></md-input>
                        <small *ngIf="!some_obj.controls.name.valid">Nome é requerido</small>
                    </div>
                    <md-input type="number" placeholder="Cost" formControlName="attr" required></md-input>
                </div>
            </div>
        </div>
    </div>
    <button type="submit" md-raised-button color="primary" [disabled]="!myForm.valid">Submit</button>
</form>
<pre>form value: <br>{{myForm.value | json}}</pre>


回答2:

I experienced the same issue when is used brackets around nested formControlName. For example (this is wrong):

<div formArrayName="options">
            <ion-row *ngFor="let option of addProductForm.controls.options.controls; index as i;">
              <ion-col no-padding [formGroupName]="i">
                <ion-item>
                  <ion-label floating>Name</ion-label>
                  <ion-input type="text" [formControlName]="option_name"></ion-input>
                </ion-item>
              </ion-col>
            </ion-row>
          </div>

formControlName="option_name" must be WITHOUT brackets



回答3:

With nested Array. Here is a code sample wich I have tested and runs perfectly in angular6:

app.component.ts:

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {

  proxyMedia: FormArray;
  formGroup: FormGroup;

  constructor(
    public formBuilder: FormBuilder
  ) {}

  ngOnInit() {
    this.formGroup = this.formBuilder.group({
      test_name: ['', [Validators.required]],
      tests: this.formBuilder.array([
        this.initTestsForm()
      ])
    });
  }

  initTestsForm(): any {
    return this.formBuilder.group({
      test_num: '',
      categorie: '',
      responses: this.formBuilder.array([
        this.initElement('responses')
      ])
    });
  }

  initElement(elementName: string): FormGroup {
    if(elementName === 'proxy_media') {
      return this.formBuilder.group(
        {
          prefixe: 'prefixe',
          confid: 'confid'
        }
      );
    } else if(elementName === 'tests') {
      return this.formBuilder.group({
        test_num: ['test_num', [Validators.required, Validators.minLength(2)]],
        categorie: ['categorie', [Validators.required, Validators.minLength(2)]],
        responses: this.formBuilder.array([
          this.initElement('responses')
        ])
      });
    } else if(elementName === 'responses') {
      return this.formBuilder.group({
        code_response: ['code_response', Validators.required],
        log_level: ['log_level', Validators.required]
      });
    }
  }

  addElement(formGroup: FormGroup, elementName: string): void {
    const control = < FormArray > formGroup.controls[elementName];
    control.push(this.initElement(elementName));
  }

  removeElement(formGroup: FormGroup, elementName: string, index: number): void {
    const control = <FormArray>formGroup.controls[elementName];
    control.removeAt(index);
  }

  onSubmit(o: any) {
    console.log(o);
  }

  debug(data: any) {
    console.warn('debug: data ');
    console.warn(data);
    console.warn('stop');
  }

}
</pre>

<b>app.component.html:</b>

<h1> Avaibility tests</h1>

<form [formGroup]="formGroup" novalidate (ngSubmit)="onSubmit(formGroup)">
    <input placeholder="Test name" formControlName="test_name">

    <hr>
    <h3>Tests</h3>
    <div formArrayName="tests">
        <div [formGroupName]="testIndex" *ngFor="let test of formGroup.controls.tests.controls; let testIndex = index">
          <h2> Test number #{{testIndex}}</h2>
          <input placeholder="Test number" formControlName="test_num">
          <input placeholder="Category" formControlName="categorie">

            <h3>Responses</h3>
            <hr>
            <div formArrayName="responses">
                <div [formGroupName]="responseIndex" *ngFor="let response of test.controls.responses.controls; let responseIndex = index">
                    <div>
                      <h4> HTTP Response #{{testIndex}}.{{responseIndex}}</h4>
                      <input placeholder="Code response" formControlName="code_response">
                      <small *ngIf="!response.controls.code_response.valid">code response required</small>
                      <input placeholder="Log level" formControlName="log_level">
                    </div>
                    <button type="button" (click)='removeElement(test,"responses",responseIndex)'>Remove Response</button>
                </div>
            </div>
            <hr>
            <button type="button" (click)="addElement(test,'responses')">Add Response</button>
            <br><br>
            <button type="button" (click)='removeElement(formGroup,"tests",testIndex)'>Remove Test</button>
        </div>
    </div>

    <hr>
    <button type="button" (click)='addElement(formGroup,"tests")'>Add Test</button>

    <hr>
    <button type="submit" md-raised-button color="primary" [disabled]="!formGroup.valid">Submit</button>
</form>

<br><br>
</pre>

<b>Add this code at the bottom to debug:</b>
<pre>form value: <br>{{formGroup.value | json}}</pre>