How to iterate formgroup with array in Angular2

Jason Stokes picture Jason Stokes · Aug 30, 2016 · Viewed 35.1k times · Source

I'm working on a model driven form and I can't get it to add items to a list being displayed with ngFor. I'm currently getting an error when trying to iterate my list.

Error:

Error: Cannot find control with path: 'locations -> i'
    at new BaseException (exceptions.ts:21)
    at _throwError (shared.ts:80)
    at Object.setUpFormContainer (shared.ts:66)
    at FormGroupDirective.addFormGroup (form_group_directive.ts:74)
    at FormGroupName.AbstractFormGroupDirective.ngOnInit (abstract_form_group_directive.ts:37)
    at DebugAppView._View_PersonFormComponent4.detectChangesInternal (PersonFormComponent.ngfactory.js:3197)
    at DebugAppView.AppView.detectChanges (view.ts:260)
    at DebugAppView.detectChanges (view.ts:378)
    at DebugAppView.AppView.detectContentChildrenChanges (view.ts:279)
    at DebugAppView._View_PersonFormComponent2.detectChangesInternal (PersonFormComponent.ngfactory.js:1995)
Raw  person-form-builder.service.ts

formBuilderService:

import {Injectable} from "@angular/core";
import {Validators, FormBuilder} from '@angular/forms';

import {Person} from './../components/person/person';

@Injectable()

export class PersonFormBuilderService {

    constructor(private fb: FormBuilder) {
    }

    getForm(person: Person) {
        return this.fb.group({
            _id: [person._id],
            name: this.fb.group({
                first: [person.name.first],
                middle: [person.name.middle],
                last: [person.name.last],
                full: [person.name.full],
            }),
            locations: this.fb.array([
                this.initLocation(),
            ]),
            files: this.fb.array([
                this.initFiles(),
            ]),
            skills: this.fb.array([
                this.initSkills(),
            ]),
        });
    }

    initLocation() {
        return this.fb.group({
            isDefault: [false, Validators.required],
            location: ['', Validators.required],
            roles: ['', Validators.required],
            isContact: [false, Validators.required],
            contactPhone: ['', Validators.required],
            contactPhoneExt: ['', Validators.required],
        });
    }

    initFiles() {
        return this.fb.group({
            originalName: ['', Validators.required],
        });
    }

    initSkills() {
        return this.fb.group({
            name: ['', Validators.required],
            isrequired: [false, Validators.required],
            expireDate: ['', Validators.required],
            canOverride: [false, Validators.required],
        });
    }

    addLocation(control) {
        control.push(this.initLocation());
    }

    removeLocation(i: number, control) {
        control.removeAt(i);
    }
}

form:

<div formGroup="form">
  <div formArrayName="locations">
      <div *ngFor="let location of form.controls.locations.controls; let i=index">
          <span>Location {{i + 1}}</span>
          <div formGroupName="i">
              <label class="mdl-checkbox mdl-js-checkbox mdl-js-ripple-effect">
                  <input type="checkbox" class="mdl-checkbox__input"
                         formControlName="isDefault" [checked]="">
              </label>
          </div>
      </div>
  </div>
</div>

form-component.ts:

import {Component} from '@angular/core';
import {FormGroup, FormArray} from '@angular/forms';

@Component({
    moduleId: module.id,
    selector: 'person-form-component',
    templateUrl: 'person-form.component.html',
    providers: [PersonService, PersonFormBuilderService]
})

export class PersonFormComponent {
  getPerson(personId) {
      this.personService.getPerson(this.personId).subscribe(res => {
          this.person = res.data;
          this.form = this.personFormBuilderService.getForm(this.person);
      });
  }

  addLocation() {
      let control = <FormArray> this.form.controls['locations'];
      this.personFormBuilderService.addLocation(control);
  }
}

https://gist.github.com/jpstokes/11551ff5d8c76514005c6c9fd8a554dd

Answer

ClaytonK picture ClaytonK · Feb 9, 2017

Here is the solution that worked for me. https://plnkr.co/edit/cs244r

HTML Template:

<div>
      <h2>RegionId: {{regionId}}</h2>
      <h2>Region:  {{region.name}}</h2>
    </div>
    <form [formGroup]="regionFormGroup">
      Region Name: <input type="text" formControlName="regionName" [(ngModel)]="region.name" />
      <div formArrayName="customersArray">
      <table class="simple-table">
        <tr>
          <th>Customer</th>
          <th>Current Period</th>
          <th>Previous Period</th>
        </tr>
        <tbody>
          <tr [formGroupName]="i" *ngFor="let customerGroup of regionFormGroup.controls.customersArray.controls; let i = index">
            <td>{{region.customers[i].name}} - index {{i}}</td>
            <td><input type="text" formControlName="currentPeriod" [(ngModel)]="region.customers[i].currentPeriod"/></td>
            <td><input type="text" formControlName="previousPeriod" [(ngModel)]="region.customers[i].previousPeriod"></td>
          </tr>
        </tbody>
      </table>
      </div> <!-- end: div FormArrayName -->
    </form>

Component.ts

export class AppComponent  implements OnInit {

  regionId: number = 5;
  region: RegionModel;
  regionFormGroup: FormGroup;

  constructor( private customerService: CustomerService,private fb: FormBuilder) {  }

  ngOnInit(): void {

    // initialize form
    this.regionFormGroup = new FormGroup({
      regionName: new FormControl(''),
      customersArray: new FormArray([])
    });

    // Retrieve data from datasource
    this.customerService.getCustomerByRegion(5)
      .subscribe( (reg: RegionModel )  => {
        this.region = reg;
        this.buildForm();
      });
  }

  buildForm = () : void => {

    const customersControls = <FormArray>this.regionFormGroup.controls['customersArray'];

    this.region.customers.forEach( (cust : CustomerModel) => {
      customersControls.push(this.createCustomerFormGroup(cust));
       console.log(customersControls);
    });
  }

  createCustomerFormGroup(cust: CustomerModel) {

        return this.fb.group({
            currentPeriod: [''],
            previousPeriod: ['']
        });
    }
}