Reputation: 2471
I have a reactive form with a somewhat complex structure. I am struggling with creating form controls and validations for an array of form group objects in the form.
Similar questions have been asked on here before, but I wasn't able to find any answers for how to bind to an array of form groups.
The parent form group Record
has top-level properties and then a nested array of form group objects Items
.
Each Item has the properties "Title" and "Description". The Title and Description need controls and validations. I do not necessarily know how many Items will be required on the form, it is dependent upon how many "Items" each "Record" has when the form is initialized with default values.
Here is a stackblitz: https://stackblitz.com/edit/angular-nkhgsj
Here are some snippets from the code:
Example data:
// Placeholder to emulate data from an API response
recordAPIResponse =
{
"ID": 12345,
"Items": [
{
"Title": "Example title 1",
"Description": "Example description 1"
},
{
"Title": "Example title 2",
"Description": "Example description 2"
},
{
"Title": "Example title 3",
"Description": "Example description 3"
}
]
};
The form is initialized with the Record values that are returned from an API response.
private initRecordForm(record: Record) {
// Create items controls with validations
const itemsCtrls = this.createItemsCtrls(record.Items);
const recordForm = this.fb.group({
ID: [record.ID, Validators.compose([
Validators.minLength(3),
Validators.maxLength(10),
Validators.pattern(/^[0-9]*$/)
])],
items: itemsCtrls
});
return recordForm;
}
The "Items" controls are pre-populated and then placed into the parent form group when it is initialized.
// Create a form control for each object in Items array
private createItemsCtrls(items: Item[]) {
// Initilize an empty form array
let formArrayCtrls = this.fb.array([]);
// Iterate each value
if (items && items.length > 0) {
items.forEach(item => {
// Create a new form control for each array value and push to form array
formArrayCtrls.push(
this.fb.group({
Title: [item.Title, Validators.compose([
Validators.minLength(5),
Validators.maxLength(10),
Validators.pattern(/^[0-9A-Za-z]*$/)
])],
Description: [item.Description, Validators.compose([
Validators.minLength(1),
Validators.maxLength(20),
Validators.pattern(/^[0-9A-Za-z]*$/)
])]
})
);
});
}
console.log(formArrayCtrls);
return formArrayCtrls;
}
This works, the form group is created with the nested array of form group objects.
However, I do not now know to bind the nested array of Form Group objects to the template, capture the latest value and perform the validations.
<form [formGroup]="recordForm" (submit)="onSubmit()">
<div>
<label>ID: </label>
<input formControlName="ID">
</div>
<div formArrayName="Items">
<ng-container *ngFor="let item of record.Items">
<label>Title: </label>
<input formControlName="Title">
<label>Description: </label>
<input formControlName="Description">
<br />
</ng-container>
</div>
<button type="submit">Submit</button>
</form>
Error message:
ERROR Error: Cannot find control with path: 'Items -> Title'
Upvotes: 3
Views: 7920
Reputation: 29325
So your form array is actually an array of form groups, and each form groups "name" is its index in the array, so you need to let Angular know the index to access it:
<ng-container
*ngFor="let item of record.Items; let i = index"
[formGroupName]="i">
</ng-container>
blitz link: https://stackblitz.com/edit/angular-nznpfj?file=src/app/app.component.html
Upvotes: 8