I need to render the Dynamic fields in the Angular. But, that one is solved. Then what the issue is when i render the Nested Array or a Group it's not rendering properly.
We've to add or remove functionality also needed.
As of now, we're not able to render the below data in the Form.
{
type: 'group',
name: 'current_job',
label: 'Current Job',
children: [
{
type: 'text',
label: 'Company',
name: 'company',
value: 'Skillmine',
},
{
type: 'array',
name: 'company_addresses',
label: 'Company Addresses',
value: [
{ _id: '1', street: '1600 Amphitheatre Parkway', city: 'Mountain View', state: 'CA' },
{ _id: '2', street: 'C. Montes Urales 445', city: 'Ciudad de México', state: 'Distrito Federal' }
],
children: [
{
type: 'text',
label: 'Street',
name: 'street',
},
{
type: 'text',
label: 'City',
name: 'city',
},
{
type: 'text',
label: 'State',
name: 'state',
}
]
}
]
},
This is my App.component.html code..
<form [formGroup]="form" (ngSubmit)="onSubmit()">
<div *ngFor="let field of formConfig">
<div [ngSwitch]="field.type">
<label *ngIf="field.label">{{ field.label }}</label>
<!-- Handle form controls -->
<input *ngSwitchCase="'text'" [formControlName]="field.name" />
<input *ngSwitchCase="'number'" type="number" [formControlName]="field.name" />
<select *ngSwitchCase="'select'" [formControlName]="field.name">
<option *ngFor="let option of field.options" [value]="option.key">{{ option.value }}</option>
</select>
<!-- Handle nested arrays -->
<div *ngSwitchCase="'array'" [formArrayName]="field.name">
<div *ngFor="let group of getFormArray(field.name)?.controls; let i = index" [formGroupName]="i">
<ng-container *ngFor="let child of field.children">
<label *ngIf="child.label">{{ child.label }}</label>
<input *ngIf="child.type === 'text'" [formControlName]="child.name" />
<input *ngIf="child.type === 'number'" type="number" [formControlName]="child.name" />
<!-- Handle nested arrays within arrays -->
<ng-container *ngIf="child.type === 'array'">
<div [formArrayName]="child.name">
<div *ngFor="let innerGroup of getFormArray(child.name)?.controls; let j = index" [formGroupName]="j">
<ng-container *ngFor="let innerChild of child.children">
<label *ngIf="innerChild.label">{{ innerChild.label }}</label>
<input *ngIf="innerChild.type === 'text'" [formControlName]="innerChild.name" />
<input *ngIf="innerChild.type === 'number'" type="number" [formControlName]="innerChild.name" />
</ng-container>
<button type="button" (click)="removeArrayControl(child.name, j)">Remove</button>
</div>
<button type="button" (click)="addArrayControl(child.name)">Add</button>
</div>
</ng-container>
</ng-container>
<button type="button" (click)="removeArrayControl(field.name, i)">Remove</button>
</div>
<button type="button" (click)="addArrayControl(field.name)">Add</button>
</div>
<!-- Handle nested groups -->
<div *ngSwitchCase="'group'" [formGroupName]="field.name">
<ng-container *ngFor="let child of field.children">
<label *ngIf="child.label">{{ child.label }}</label>
<input *ngIf="child.type === 'text'" [formControlName]="child.name" />
<input *ngIf="child.type === 'number'" type="number" [formControlName]="child.name" />
<!-- Handle nested arrays within groups -->
<ng-container *ngIf="child.type === 'array'">
<div [formArrayName]="child.name">
<div *ngFor="let innerGroup of getFormArray(child.name)?.controls; let j = index" [formGroupName]="j">
<ng-container *ngFor="let innerChild of child.children">
<label *ngIf="innerChild.label">{{ innerChild.label }}</label>
<input *ngIf="innerChild.type === 'text'" [formControlName]="innerChild.name" />
<input *ngIf="innerChild.type === 'number'" type="number" [formControlName]="innerChild.name" />
</ng-container>
<button type="button" (click)="removeArrayControl(child.name, j)">Remove</button>
</div>
<button type="button" (click)="addArrayControl(child.name)">Add</button>
</div>
</ng-container>
</ng-container>
</div>
</div>
</div>
<button type="submit">Submit</button>
</form>
This is my App.Component.ts code..
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, FormArray, FormControl, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
form!: FormGroup;
formConfig: FieldConfig[] = [
{
type: 'text',
label: 'Given Name',
name: 'given_name',
value: 'Baskaran',
},
{
type: 'array',
name: 'mobile_numbers',
label: 'Mobile Numbers',
value: [
{ _id: '1', mobile_number: '+9393393939' },
{ _id: '2', mobile_number: '+9393838484' }
],
children: [
{
type: 'text',
name: 'mobile_number',
}
]
},
{
type: 'group',
name: 'current_job',
label: 'Current Job',
children: [
{
type: 'text',
label: 'Company',
name: 'company',
value: 'Skillmine',
},
{
type: 'array',
name: 'company_addresses',
label: 'Company Addresses',
value: [
{ _id: '1', street: '1600 Amphitheatre Parkway', city: 'Mountain View', state: 'CA' },
{ _id: '2', street: 'C. Montes Urales 445', city: 'Ciudad de México', state: 'Distrito Federal' }
],
children: [
{
type: 'text',
label: 'Street',
name: 'street',
},
{
type: 'text',
label: 'City',
name: 'city',
},
{
type: 'text',
label: 'State',
name: 'state',
}
]
}
]
},
];
constructor(private fb: FormBuilder) { }
ngOnInit(): void {
this.form = this.fb.group(this.createFormGroup(this.formConfig));
}
createFormGroup(config: FieldConfig[]): { [key: string]: FormControl | FormArray | FormGroup } {
const group: { [key: string]: FormControl | FormArray | FormGroup } = {};
config.forEach(field => {
if (field.type === 'array') {
group[field.name] = this.createFormArray(field);
} else if (field.type === 'group') {
group[field.name] = this.fb.group(this.createFormGroup(field.children || []));
} else {
group[field.name] = this.createFormControl(field);
}
});
return group;
}
createFormControl(config: FieldConfig): FormControl {
return new FormControl(config.value || '', config.validators || []);
}
createFormArray(config: FieldConfig): FormArray {
const formArray = this.fb.array<FormGroup>([]);
const initialValues = config.value || [];
initialValues.forEach((value: any) => {
const groupConfig = this.createGroupConfig(config.children || [], value);
formArray.push(this.fb.group(groupConfig));
});
return formArray;
}
createGroupConfig(config: FieldConfig[], value: any): { [key: string]: FormControl | FormArray | FormGroup } {
return config.reduce((acc, child) => {
if (child.type === 'array') {
acc[child.name] = this.createFormArray(child);
} else if (child.type === 'group') {
acc[child.name] = this.fb.group(this.createFormGroup(child.children || []));
} else {
acc[child.name] = this.createFormControl({ ...child, value: value[child.name] });
}
return acc;
}, {} as { [key: string]: FormControl | FormArray | FormGroup });
}
getFormArray(controlName: string): FormArray | null {
const control = this.form.get(controlName);
return control instanceof FormArray ? control : null;
}
addArrayControl(controlName: string): void {
const array = this.getFormArray(controlName);
if (array) {
const config = this.formConfig.find(field => field.name === controlName);
if (config && config.children) {
const newGroup = this.fb.group(this.createGroupConfig(config.children, {}));
array.push(newGroup);
}
}
}
removeArrayControl(controlName: string, index: number): void {
const array = this.getFormArray(controlName);
if (array) {
array.removeAt(index);
}
}
onSubmit(): void {
console.log(this.form.value);
}
}
export interface FieldConfig {
type: 'text' | 'number' | 'select' | 'array' | 'group';
label?: string;
name: string;
options?: { key: string; value: string }[];
value?: any;
validators?: any[];
children?: FieldConfig[];
_id?: string;
}
See your function 'getFormArray'
getFormArray(controlName: string): FormArray | null {
const control = this.form.get(controlName) as FormArray;
return control instanceof FormArray ? control : null;
}
As you pass, when inner FormArray 'child.name', you're passing simply, e.g. "company_addresses", you should pass "current_job.company_addresses", so replace it
Yes,the method 'get' of a formGroup can use "dot" notation.
<ng-container *ngIf="child.type === 'array'">
<div [formArrayName]="child.name">
<!--you loop over gorm.get("field.name"+"."+child.name")-->
<div *ngFor="let innerGroup of getFormArray(field.name+'.'+child.name)?.controls; let j = index" [formGroupName]="j">
<ng-container *ngFor="let innerChild of child.children">
<label *ngIf="innerChild.label">{{ innerChild.label }}</label>
<input *ngIf="innerChild.type === 'text'" [formControlName]="innerChild.name" />
<input *ngIf="innerChild.type === 'number'" type="number" [formControlName]="innerChild.name" />
</ng-container>
<!--see that you should remove the j element of "field.name"+"."+child.name"-->
<button type="button" (click)="removeArrayControl(field.name+'.'+child.name, j)">Remove</button>
</div>
<!--Carefull, pass the formArrayName and the childName-->
<button type="button" (click)="addArrayControl(field.name,child.name)">Add</button>
</div>
</ng-container>
NOTE: Change your addArrayControl to take account the changes.