12

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

1
  • Have a look at this. Kara Erickson is showing a way to do this. Starting at 30min. Commented Aug 30, 2016 at 20:10

2 Answers 2

13

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: ['']
        });
    }
}
Sign up to request clarification or add additional context in comments.

7 Comments

This really helps. Vote up.
it will give issues in aot
@HD.. Can you be more specific? What type of issues? And why? Thanks.
controls.customersArray.controls controls of control wont work in AOT
Also, why are you still including ngModel attributes?
|
11

Fixed it!!!

So apparently I need to read up on Angular2 Fundamentals because I thought my mistake was legal. Basically, I ignored the square brackets around formGroupName in the tutorial because I've done it numerous times before without issue, but not this time. So to fix I simply added the brackets:

<div formGroupName="i"> => <div [formGroupName]="i">

1 Comment

Or formGroupName="{{i}}"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.