Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular 2: Can't add form group to form array in reactive Forms

I'm building dynamic form and want to add form groups 'on the fly'.

Here is my code which almost works:

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

export class CombinedComponent implements OnInit {

    ltsForm: FormGroup;

    constructor(private formBuilder: FormBuilder) {
    }

    ngOnInit() {
        this.ltsForm = this.initFormGroup();

        // init products
        for (let i = 0; i < 3; i++) { // add dynamically products
            this.addProduct();
        }
        
        console.log(this.ltsForm); // Array 'prods' is empty
    }

    // initialize form group, but don't add products yet because they will be added dynamically later
    initFormGroup() {
        let group = this.formBuilder.group({
            products: this.initProductGroup()
        });

        return group;
    }

    initProductGroup() {
        let group = this.formBuilder.group(
            {
                //initialize empty formbuilder array
                prods: this.formBuilder.array([])
            }
        );

        return group;
    }

    initProducts() {
        return this.formBuilder.group({
            id: [''],
            value: false, // checkbox value
        });
    }

    addProduct() {
        <FormArray>this.ltsForm.controls['products'].value.prods.push(this.initProducts());

        console.log(this.ltsForm); // Array 'prods' contains 3 FormGroup elements
    }
} 

Template:

<form [formGroup]="ltsForm"
      novalidate
      (ngSubmit)="save(ltsForm)">

    <div formGroupName="products">
        <div formArrayName="prods">

            <div *ngFor="let product of ltsForm.controls.products.value.prods.controls; let i = index">
                <div [formGroupName]="i">
                    <input type="checkbox"
                           formControlName="value"
                           id="product_{{ i }}"
                           name="product_{{ i }}">
                </div>
            </div>

        </div>
    </div>

    <button type="submit"
            [disabled]="!ltsForm.valid">
        Submit
    </button>
</form>

In method addProduct() I push the whole FormGroup element to the 'prods' array. So at the end the output from console in ngOnInit() contains just an empty 'prods' array, while the array from console output in addProduct() method has 3 elements. It looks like this.ltsForm looses its reference and isn't updating. Any ideas?

UPD: Just found out that if I remove the whole content from template, I get the 'prods' filled with data.

like image 331
rvaliev Avatar asked Jan 07 '17 03:01

rvaliev


1 Answers

There were a number of small mistakes and complexities, so I pared down your example and built it back up. The Angular team had examples of a nested form array and nested form group that were very helpful. Here was the process (and plnkr):

  1. Got a simple group working: { projects: '' }.
  2. Got a group with an array of controls working: { projects: ['a', 'b', 'c'] }. I skipped prods, it seemed unnecessary.

    <form [formGroup]="ltsForm" novalidate (ngSubmit)="save()">
      <div formArrayName="products">
        <div *ngFor="let p of products.controls; let i=index">
          <input [formControlName]="i">
        </div>
      </div>
      <button type="submit" [disabled]="!ltsForm.valid">
        Submit
      </button>
    </form>
    
    ...
    
    export class CombinedComponent implements OnInit {
    
      ltsForm: FormGroup;
    
      get products() { return this.ltsForm.get('products'); }
    
      constructor(private formBuilder: FormBuilder) {}
    
      ngOnInit() {
          this.ltsForm = this.formBuilder.group({
            products: this.formBuilder.array([])
          });
    
          for (let i = 0; i < 3; ++i) {
            this.addProduct();
          }
      }
    
      addProduct() {
        this.products.push(this.formBuilder.control(''));
      }
    
      save() {
        console.log(this.ltsForm.value);
      }
    } 
    
  3. Final step replace controls in the array with groups:

    @Component({
      selector: 'combined-component',
      template: `
        <form [formGroup]="ltsForm" novalidate (ngSubmit)="save()">
          <div formArrayName="products">
            <div *ngFor="let p of products.controls; let i=index">
              <div [formGroupName]="i">
                <input formControlName="id">
                <input type="checkbox" formControlName="value">
              </div>
            </div>
          </div>
          <button type="submit" [disabled]="!ltsForm.valid">
              Submit
          </button>
        </form>
      `
    })
    export class CombinedComponent implements OnInit {
    
      ltsForm: FormGroup;
    
      get products() { return this.ltsForm.get('products'); }
    
      constructor(private formBuilder: FormBuilder) {}
    
      ngOnInit() {
          this.ltsForm = this.formBuilder.group({
            products: this.formBuilder.array([])
          });
    
          for (let i = 0; i < 3; ++i) {
            this.addProduct();
          }
      }
    
      addProduct() {
        this.products.push(this.formBuilder.group({
          id: '',
          value: false
        }));
      }
    
      save() {
        console.log(this.ltsForm.value);
      }
    } 
    
like image 96
stealththeninja Avatar answered Oct 21 '22 15:10

stealththeninja