I have a Nested Reactive Form Array that I need to duplicate. For some reason, I'm only. able to duplicate one part of the array. Here is a link to the code. I've updated the code to include a cascading dropdown I did with ngModel. It seems when I add another array my selection changes with my second one. I know it's something to do with my model I just can't figure it out. I've updated the StackBlitz link below. thanks.
import 'zone.js/dist/zone';
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import {
FormArray,
FormBuilder,
FormControl,
FormGroup,
FormsModule,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
@Component({
selector: 'my-app',
standalone: true,
imports: [CommonModule, FormsModule, ReactiveFormsModule],
templateUrl: 'main.html',
})
export class App {
selectedCategoryList: String = "--Choose Category--";
categoryList: Array<any> = [
{
name: "Test One",
subCategoryList: [
{
typeList: [
""
],
name: "Test 1"
},
{
typeList: [
""
],
name: "Test 1.1"
},
{
typeList: [
""
],
name: "Test 2.2"
}
]
},
{
name: "Test Two",
subCategoryList: [
{
typeList: [
""
],
name: "Test 2"
},
{
typeList: [
""
],
name: "Test 2.1"
},
{
typeList: [
""
],
name: "Test 2.2"
}
]
},
{
name: "Test Three",
subCategoryList: [
{
typeList: [
""
],
name: "Test 3"
}
]
}
];
selectedCountry: String = "--Choose Document Category--";
subCategoryList: Array<any> = [];
typeList: Array<any> = [];
form!: FormGroup;
constructor(private fb: FormBuilder) {}
documentForm = this.fb.group({
appCode: ['123'],
accountType: [''],
emailId: ['test@aol.com'],
IDV: [''],
envelopeRequest: this.fb.group({
documents: this.fb.array([]),
}),
});
ngOnInit() {
// this.form = this.fb.group({
// documents: this.fb.array([]),
// });
this.initDocument();
}
changeCategoryList(category: any) {
this.subCategoryList = this.categoryList.find((cat: any) => cat.name == category.target.value).subCategoryList;
}
changeSubCategoryList(subList: any) {
this.typeList = this.categoryList.find((cat: any) => cat.name == this.selectedCountry).subCategoryList.find((stat: any) => stat.name == subList.target.value).typeList;
}
initDocument() {
this.documents.push(this.newDocument());
let documentIndex = this.documents.length - 1;
this.newDocumentFieldGroup(documentIndex);
//console.log(this.documentFields(0).controls.length);
}
newDocument() {
return this.fb.group({
name: new FormControl(''),
documentId: new FormControl(''),
documentFields: this.fb.array([]),
});
}
newDocumentFieldGroup(comIndex: number) {
this.documentFields(comIndex).push(this.newDocumentFields());
this.documentFields(comIndex).push(
this.newDocumentFields('documentSubCategory')
);
}
newDocumentFields(name = 'documentCategory') {
return this.fb.group({
name: new FormControl(name),
value: new FormControl(''),
});
}
get documents() {
// Problem was from here, should use `documentForm` --> envelopeRequest --> documents
return this.documentForm.controls.envelopeRequest.controls
.documents as FormArray;
}
documentFields(comIndex: number) {
return this.documents.controls[`${comIndex}`].get(
'documentFields'
) as FormArray;
}
documentFieldsIndexes(comIndex: number) {
return Array(this.documentFields(comIndex).controls.length / 2)
.fill(0)
.map((x, i) => i * 2);
}
submit() {
console.log(this.documentForm.value);
}
}
bootstrapApplication(App);
Here is the HTML
<form [formGroup]="documentForm" (ngSubmit)="submit()">
<ng-container formGroupName="envelopeRequest">
<button (click)='initDocument()'>Add Document</button>
<ng-container formArrayName="documents">
<ng-container
*ngFor="let control of documents.controls; let comIndex=index"
>
<ng-container [formGroupName]="comIndex">
<div class="row justify-content-start mb-3">
<div class="col-md-1">
{{comIndex}}
<input
type="hidden"
id="fname"
name="fname"
formControlName="documentId"
/>
<label for="formFile" class="col-form-label">File:</label>
</div>
<div class="col-md-6">
<input
class="form-control"
type="file"
id="file"
name="file"
formControlName="name"
/>
</div>
</div>
<ng-container formArrayName="documentFields">
<ng-container
*ngFor="let skillIndex of documentFieldsIndexes(comIndex);"
>
<div class="row mb-3">
<label class="col-sm-1 col-form-label">Category:</label>
<div class="col-md-2">
<ng-container [formGroupName]="skillIndex">
<select id="" class="form-select" formControlName="value" [(ngModel)]= "selectedCategoryList" (change)="changeCategoryList($event)">
<option selected>Select</option>
<option *ngFor="let c of categoryList">
{{c.name}}
</option>
</select>
</ng-container>
</div>
<label class="col-auto col-form-label">Sub-Category:</label>
<div class="col-md-2">
<ng-container [formGroupName]="skillIndex + 1">
<select id="" class="form-select" formControlName="value" (change)="changeSubCategoryList($event)">
<option selected>Select</option>
<option *ngFor="let s of subCategoryList">{{s.name}}</option>
</select>
</ng-container>
</div>
</div>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
<button type="submit">Submit</button>
</form>
<pre>{{this.documentForm.value | json }}
You will see only the input file works but my other fields only repeat in the wrong spot. Any help will do. thanks
The issue was the below code in the initDocument
method:
this.newDocumentFieldGroup(0);
which will add the new nested FormGroup
s into the documentFields
FormArray
for the first element of documents
FormArray
.
You have to provide the latest index for the FormGroup
in the documents
FormArray
. Do remember that the array index starts from 0.
initDocument() {
this.documents.push(this.newDocument());
let documentIndex = this.documents.length - 1;
this.newDocumentFieldGroup(documentIndex);
//console.log(this.documentFields(0).controls.length);
}
Updated:
The reason why all the categories were updated to the same value as you are using [(ngModel)]
:
<select id="" class="form-select" formControlName="value" [(ngModel)]= "selectedCategoryList" (change)="changeCategoryList($event)">
This results that all the form controls will share the same value. Remove the [(ngModel)]
as it is not needed and you are using the Reactive Form.
<select id="" class="form-select" formControlName="value" (change)="changeCategoryList($event)">
...
</select>
If you are keen to set the default value at the beginning, would suggest using the patchValue
method.
newDocumentFields(name = 'documentCategory') {
return this.fb.group({
name: new FormControl(name),
value: new FormControl(
name == 'documentCategory' ? '--Choose Document Category--' : ''
),
});
}