-1

enter image description here

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;
}

1 Answer 1

1

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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.