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
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: ['']
});
}
}