Search code examples
javascriptangulartypescriptangular-reactive-formsangular4-forms

Angular dynamic form nested fields


With the help of https://angular.io/guide/dynamic-form, i am making a dynamic form where i am in the need to display two fields at first.

  new TextboxQuestion({
    key: 'firstName',
    label: 'First name',
    value: '',
    required: true,
    order: 1
  }),

  new TextboxQuestion({
    key: 'lastName',
    label: 'Last name',
    value: '',
    required: true,
    order: 2
  }),

These two fields needs to be at first on loading.

After this i will have two buttons as add and remove.

  <button (click)="addNew()"> Add </button> &nbsp;&nbsp;&nbsp;
  <button (click)="removeNew()"> Remove </button> <br><br>

By clicking add, i need to display the next two fields (the following fields),

  new TextboxQuestion({
    key: 'emailAddress',
    label: 'Email',
    type: 'email',
    order: 3
  }),

  new DropdownQuestion({
    key: 'brave',
    label: 'Bravery Rating',
    options: [
      {key: 'solid',  value: 'Solid'},
      {key: 'great',  value: 'Great'},
      {key: 'good',   value: 'Good'},
      {key: 'unproven', value: 'Unproven'}
    ],
    order: 4
  })

Order 1 & 2 in the initial state and after clicking add the next two order 3 & 4 needs to get displayed.

KIndly help me to achieve the result of adding child fields on click add button.

The working stackblitz which displays all at once, https://stackblitz.com/edit/angular-x4a5b6


Solution

  • Implementing dynamic form with formArray.

    Well, the things are more complex. I make a stackblik, see demo

    I'll try to explain how extends the https://angular.io/guide/dynamic-form to allow Form Array.

    The first we need make is create a new type of question, a questionArray

    import { QuestionBase } from './question-base';
    
    export class ArrayQuestion extends QuestionBase<string> {
      controlType = 'array';
      type: any;
    
      constructor(options: {} = {}) {
        super(options);
      }
    }
    

    We must change question base to add a new property "children"

    export class QuestionBase<T> {
      value: T;
      ...
      children:any[];
    
      constructor(options: {
          value?: T,
          ...
          children?:any
        } = {}) {
        this.value = options.value;
        ...
        this.children=options.children || null;
      }
    }
    

    Add change the question-control service to allow manage formArrays

    toFormGroup(questions: QuestionBase<any>[]) {
        let group: any = {};
    
        questions.forEach(question => {
          //If the control type is "array" we create a FormArray
          if (question.controlType=="array") {
             group[question.key]=new FormArray([]);
          }
          else {
            group[question.key] = question.required ? new FormControl(question.value || '', Validators.required)
              : new FormControl(question.value || '');
          }
        });
        return new FormGroup(group);
      }
    

    Well We transform the dynamic-form.component to show a FormArray

    <div *ngFor="let question of questions" class="form-row">
        <ng-container *ngIf="question.children">
            <div [formArrayName]="question.key">
                <div *ngFor="let item of form.get(question.key).controls; let i=index" [formGroupName]="i">
                    <div *ngFor="let item of question.children">
                        <app-question [question]="item" [form]="form.get(question.key).at(i)"></app-question>
                    </div>
                </div>
            </div>
        </ng-container>
        <ng-container *ngIf="!question.children">
            <app-question [question]="question" [form]="form"></app-question>
    
        </ng-container>
    </div>
    

    And just it doit. Well, how increment and decrement the formArrays? We have two buttons

      <button (click)="addControls('myArray')"> Add </button>
      <button (click)="removeControls('myArray')"> Remove </button> <br><br>
    

    And two functions addControls and removeControls

      addControls(control: string) {
        let question: any = this.questions.find(q => q.key == control);
        let children = question ? question.children : null;
        if (children)
          (this.form.get(control) as FormArray).push(this.qcs.toFormGroup(children))
      }
      removeControls(control: string){
        let array=this.form.get(control) as FormArray;
        array.removeAt(array.length-1);
      }
    

    Update I forgot add and example of questions:

    let questions: QuestionBase<any>[] = [
    
          new TextboxQuestion({
            key: 'firstName',
            label: 'First name',
            value: '',
            required: true,
            order: 1
          }),
    
          new TextboxQuestion({
            key: 'lastName',
            label: 'Last name',
            value: '',
            required: true,
            order: 2
          }),
          new ArrayQuestion({
            key: 'myArray',
            value: '',
            order: 3,
            children: [
              new TextboxQuestion({
                key: 'emailAddress',
                label: 'Email',
                type: 'email',
                order: 3
              }),
              new DropdownQuestion({
                key: 'brave',
                label: 'Bravery Rating',
                options: [
                  { key: 'solid', value: 'Solid' },
                  { key: 'great', value: 'Great' },
                  { key: 'good', value: 'Good' },
                  { key: 'unproven', value: 'Unproven' }
                ],
                order: 4
              })
            ]
          })
        ];