Add item in dynamic reactive form in Angular

2020-02-12 14:14发布

问题:

I am trying to build a nested reactive form in Angular 5. I am able to push items at one level, but unable to push item at second level.

ngOnInit() {
 this.orderForm = this.formBuilder.group({
  customerName: '',
  email: '',
  items: this.formBuilder.array([this.createItem()])
});
}

 createItem(): FormGroup {
    return this.formBuilder.group({
      name: 'a',
      description: 'b',
      price: 'c',
      //subItems: this.formBuilder.array([this.createSubItem()])
    });
 }

 //createSubItem(): FormGroup {
    //return this.formBuilder.group({
      //subname: 'abc'
    //});
 //}

 addItem(): void {
    this.items = this.orderForm.get('items') as FormArray;
    //this.subItems = this.items.get('subItems') as FormArray;
    //this.subItems.push(this.createSubItem());
    this.items.push(this.createItem());
 }

In the above code the uncommented code is working fine with push. I have further subItems inside items, and commented code is the one I am trying to achieve, but it is not working.

Can any body show me how to achieve this?

For uncommented code, I am showing data in html like this and it is working well. If any body have solution at code level, please also tell me how to update html for nested items as well.

<form [formGroup]="orderForm">
<div formArrayName="items" *ngFor="let item of orderForm.get('items').controls; let i = index;">
    <div [formGroupName]="i">
      <input formControlName="name" placeholder="Item name">
      <input formControlName="description" placeholder="Item description">
      <input formControlName="price" placeholder="Item price">
    </div>
  </div>
  <input type="button" value="sldkj" (click)="addItem()">
</form>

SOLUTION:

First of all I managed to pre-populate values like this.

 obj = [
    {
      'name': "umair",
      'description': "desc1",
      'subItems': [
        {
          'subname': 'value'
        },
        {
          'subname': 'value2'
        },
        {
          'subname': 'value3'
        }
      ]
    },
    {
      'name': "ali",
      'description': "desc2",
      'subItems': [
        {
          'subname': 'valu4'
        },
        {
          'subname': 'value5'
        },
        {
          'subname': 'value6'
        }
      ]
    }
  ]

  ngOnInit() {
    this.createForm(this.obj);
    console.log(this.orderForm);
  }

  createForm(obj) {
    var arr = [];
    for (var i = 0; i < obj.length; i++) {
      arr.push(this.createItem(obj[i]));
    }
    this.orderForm = this.formBuilder.group({
      items: this.formBuilder.array(arr)
    });
  }

  createItem(obj): FormGroup {
    var subArr = [];
    for (var i = 0; i < obj.subItems.length; i++) {
      subArr.push(this.createSubItem(obj.subItems[i]));
    }
    return this.formBuilder.group({
      name: obj.name,
      description: obj.description,
      subItems: this.formBuilder.array(subArr)
    });
  }

  createSubItem(subItem): FormGroup {
    return this.formBuilder.group({
      subname: subItem.subname
    });
  }

After help in 'marked' answer below, I managed to add subitem and populate in html like below.

 <form [formGroup]="orderForm">
  <!-- <label>{{item.get('name').value}}</label> -->
  <div formArrayName="items" *ngFor="let item of orderForm.controls.items.controls; let i = index;">
    <div formGroupName="{{i}}">
      <!-- <label>{{item.get('name').value}}</label> -->
      <input formControlName="name" placeholder="Item name">
      <input formControlName="description" placeholder="Item description">

      <input type="button" value="subAdd" (click)="addSubItems(i)">

      <div formArrayName="subItems" *ngFor="let subItem of item.controls.subItems.controls; let idx = index;">
        <div formGroupName="{{idx}}">
          <!-- <label>{{subItem.get('subname').value}}</label> -->
          <input formControlName="subname" placeholder="Item name">
        </div>
      </div>

    </div>
  </div>
</form>

code

addSubItems(_index: any): void {
    var a = this.orderForm['controls']['items']['controls'][_index] as FormGroup;
    this.subItems = a.get('subItems') as FormArray;
    var newSubItem = {
      'subname': 'value6'
    }
    this.subItems.push(this.createSubItem(newSubItem));
  }

回答1:

So first let start with understanding what you do.

You make a formarray in formarray. So this mean it have two loops. So you have to *ngFor the first and *ngFor the second. In the end this means you have an index i for your items and and j for your subItems. Then you can add addSubItems(i) for pushing your subItems.

Your html should look something like this:

<form [formGroup]="orderForm">
  <div formArrayName="items">
     <div *ngFor="let arrays of orderForm.controls.items.controls; let i = index;">
        <div formGroupName="{{i}}">
          <input formControlName="name" placeholder="Item name">
          <input formControlName="description" placeholder="Item description">
          <input formControlName="price" placeholder="Item price">
          <input type="button" value="subAdd" (click)="addSubItems(i)"> <-- here your button -->
          <div formArrayName="subItems">
             <div *ngFor="let item of arrays.controls.subItems.controls; let j = index;">
                <div formGroupName="{{j}}">
                   <input formControlName="subname" placeholder="Item subname">

Your addSubItems should look something like:

 addSubItems(_index: any): void {
    this.item = this.orderForm['controls']['items']['controls'][_index] as FormGroup;
    this.subItems = this.item.get('subItems') as FormArray;
    this.subItems.push(this.createSubItem());


回答2:

To reduce amount of code, these can little refactored addSubItems(index) to addSubItems(item):

<form [formGroup]="orderForm">
  ...
  <div formArrayName="items" *ngFor="let item of orderForm.controls.items.controls; let i = index;">
      ...
      <input type="button" value="subAdd" (click)="addSubItems(item)">
  </div>
  ...
</form>

addSubItems(item: FormGroup): void {    
    this.subItems = item.get('subItems') as FormArray;
    var newSubItem = {
      'subname': 'value6'
    }
    this.subItems.push(this.createSubItem(newSubItem));
  }

So, no need to find item by index: var a = this.orderForm['controls']['items']['controls'][_index] as FormGroup;, because you are already can pass item: FormGroup

StackBlitz Demo