Problem Statement
I am trying to create a dynamic form where parts of the interface responds to updates of the model:
- user clicks on a button
- new model entity is added to the components internal list and a new control group (with multiple child controls, each with dedicated validators) is created and attached to the form.
- each of this child controls should establish a two-way-binding which its corresponding entry in the model [(ngModel)] and propagate state up to the top-level form
Where I am Stuck
My current problem is that i do not know how to reference a new control group (stored in an ControlArray) from within the template.
All dynamic form solutions I found so far worked without a backing model and two-way-databinding.
- Angular2 form ControlGroup who hold an undefined number of Control (and the mentioned plunkr)
- ngControl with ngFor in Angular2
Code
I have created a plunkr for below code: https://plnkr.co/edit/nP6hcIXKA0jz2F8Epg2L?p=preview
My (simplyfied) data model:
class Entry {
constructor(
public _id: string,
public _title: string
) {}
}
class Data {
constructor(
public heading: string,
public entries: Entry[] = []
) {}
}
My template:
@Component({
selector: 'my-app',
template: `
<h1>Dynamic Form</h1>
<form [ngFormModel]="formModel">
<label>heading: </label>
<input type="text" [(ngModel)]="heading" [ngFormControl]="formModel.controls.heading">
<div>
<hr>
<!-- PROBLEM: how to get a reference to a single Control from within ControlGroup from within formModel.controls['entries'] that can be wired with belows <input> fields? -->
<div *ngFor="#entry of data.entries; #i = index">
<label>id: </label>
<input type="text" [(ngModel)]="entry._id" #ctrlId="ngForm"> <span><b>is valid: </b>{{ctrlId.control.valid}}</span>
<br>
<label>title: </label>
<input type="text" [(ngModel)]="entry._title" #ctrlTitle="ngForm"><span><b>is valid: </b>{{ctrlTitle.control.valid}}</span>
<hr>
</div>
</div>
</form>
<input type="button" (click)="add()" value="add new entry">
<div>{{ctrlCount}}</div>
<div>{{debug}}</div>
<div>{{debugForm}}</div>
`
})
My Component:
export class DynamicForm implements OnInit {
data:Data;
formModel:ControlGroup;
constructor(private fb:FormBuilder) {
this.formModel = fb.group({
heading: fb.control('test heading', Validators.required),
entries: fb.array([])
})
}
ngOnInit():void {
/* init the heading */
this.data = new Data('test heading');
/* init the entries > add to model, create control and add it to ControlArray */
[
new Entry('1', 'one'),
new Entry('2', 'two'),
new Entry('3', 'three'),
new Entry('4', 'four'),
].forEach((e:Entry) => this.add(e));
}
add(e:Entry):void {
let id:string = e ? e._id : '';
let title:string = e ? e._title : '';
(<ControlArray>this.formModel.controls['entries']).push(this.fb.group({
id: this.fb.control(id, Validators.required),
title: this.fb.control(title, Validators.required),
}));
this.data.entries.push(new Entry(id, title));
}
get debug() {
return JSON.stringify({debug_data: this.data});
}
get debugForm() {
return JSON.stringify({
debug_form: {
dirty: this.formModel.dirty,
pristine: this.formModel.pristine,
touched: this.formModel.touched,
untouched: this.formModel.untouched,
valid: this.formModel.valid,
}
});
}
get ctrlCount() {
return (<ControlArray>this.formModel.controls['entries']).length;
}
}