Search code examples
javascriptangularrxjsangular-reactive-formsform-control

Unable to create reactive form with this nested array of objects


I am trying to create a reactive form with a complex data structure which is doing a lot of things and I would like your help on how to create it. So basically this is the structure of the data I have which I want to use:

[
  {
     type: 'body',
     items: [
        {
           type: 'text',
           id: 1,
           value: '',
           isCustom: false,
           customValue: '' 
        }
     ]
  }
]

In items array, there could be 0 or a million items. So if there is at least one, I want to show fields in the template.

So basically there are two fields for each item. One select box and another is an input field. The select box will have some items, let's just say some options. All of the options are predefined and when one of them is selected the value field in items object gets stored in. In select box options, there is one option which is Custom. When that is selected, the value field is set to custom, isCustom is set to true and the input field shows up. When something is typed in the input field, the customValue field is where it is stored in. This is the basic functionality.

Now I can do everything from updating and storing and validation, etc. Except I am unable to do the very first thing which is setup the form control. I think I have to use Angular's FormArray here but I am now sure how.

All I need from you is a bare mininum of how to create this form control based on the data structure I have provided. Because of not able to do this I moved away from Reactive Forms to template-driven forms, which is giving me a headache with the validations.

This is what I tried so far:

TS file:

private formBuilder = new FormBuilder();

form = this.formBuilder.group({
    title: new FormControl('', {
      validators: [
        Validators.required,
        FormValidator.nonWhitespace,
        Validators.maxLength(80),
      ],
    }),
    body: this.formBuilder.array([]),
});

ngOnInit(): void {
    if (this.bodyArray.controls.length === 0) {
      this.addBodyItem();
    }
}

get bodyArray(): FormArray {
    return this.form.get('body') as FormArray;
}

createBodyItem(): FormGroup {
    // no idea what to do here    
}

addBodyItem(): void {
    this.bodyArray.push(this.createBodyItem());
}

Template File:

<div formArrayName="body">

   // no idea what to loop here

</div>

Please note that I am using the same component for both create and edit mode. So for edit mode I think the form controls need to be created in ngOnInit to show the fields.

It would be really big help if someone can help me with this.


Solution

  • Posting this for anyone's reference. The below code sample generates form controls dynamically while iterating through the array of items provided and properties based on it.

    app.component.ts :

    import { Component,OnInit } from '@angular/core';
    import {FormBuilder, FormGroup, FormArray, FormControl, Validators } from '@angular/forms';
    import * as uuid from 'uuid';
    @Component({
      selector: 'my-app',
      templateUrl: './app.component.html',
      styleUrls: [ './app.component.css' ]
    })
    export class AppComponent  implements OnInit{
      myForm: FormGroup;
      formControls = [
        { type: 'input', label: 'Name', name: 'name', inputType: 'text' },
        { type: 'input', label: 'Email', name: 'email', inputType: 'email' },
        { type: 'select', label: 'Country', name: 'country', options: [
            { label: 'United States', value: 'US' },
            { label: 'Canada', value: 'CA' },
            { label: 'Mexico', value: 'MX' }
          ]
        }
        // Add more form controls as needed
      ];
      constructor(private formBuilder: FormBuilder){}
    
      ngOnInit() {
        this.myForm = this.formBuilder.group({});
        this.formControls.forEach(control => {
          const formControl = new FormControl('', Validators.required); // Create form control
          this.myForm.addControl(control.name, formControl); // Add controls to myForm
        });
      }
     
    }
    

    app.component.html:

    <form [formGroup]="myForm">
      <ng-container *ngFor="let control of formControls">
        <div [ngSwitch]="control.type">
          <div *ngSwitchCase="'input'">
            <label>{{ control.label }}</label>
            <input [formControlName]="control.name" [type]="control.inputType">
          </div>
          <div *ngSwitchCase="'select'">
            <label>{{ control.label }}</label>
            <select [formControlName]="control.name">
              <option *ngFor="let option of control.options" [value]="option.value">{{ option.label }}</option>
            </select>
          </div>
          <!-- Add more cases for other control types as needed -->
        </div>
      </ng-container>
    </form>
    

    Refer sample working stackblitz here.