Search code examples
angularangular-forms

Angular nested dynamic form


How to make nested form in the way from angular.io documentation:

https://stackblitz.com/angular/pbdkbbnmrdg

Goal is to have two DropdownQuestion from question.service.ts in child formgroup named "details" and everything else like now in parent form...So at the end to look something like this:

{
    "firstName":"Bembasto",
    "email":"something@email.com",
    "details": {
      "dropdown1":{

      },
      "dropdown2":{

      }
    }
}

Solution

  • Uros, you need undestand how the code create the FormGroup and how create the inputs.

    We has a complex object that it's used to

    1. Create the form
    2. Show the inputs

    First we are going to create a new type of control question-group

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

    And add a new propertie to question-base

    questions:any[];
    //and change the constructor to allow give value
    constructor(options: {
          value?: T,
          ...
          questions?:any[]
        } = {}) {
        this.value = options.value;
        ...
        this.questions = options.questions || [];
      }
    

    Take a look how the code create the form. It's made in the question-control.service. Change the function toFormGroup to take account the typeControl "group"

    toFormGroup(questions: QuestionBase<any>[] ) {
        let group: any = {};
    
        questions.forEach(question => {
          group[question.key] =  (question.controlType=='group')?
          this.toFormGroup(question.questions)
          :question.required ? new FormControl(question.value || '', Validators.required)
                                                  : new FormControl(question.value || '');
        });
        return new FormGroup(group);
      }
    

    Yes, we are using a recursive function. The idea we have is that we are going to have a question object like, e.g.

    let questions: QuestionBase<any>[] = [
      new DropdownQuestion({
        ...
      }),
      new TextboxQuestion({
        ...
      })
      , new GroupQuestion(
        {
          key: 'details',
          label: 'Details',
          order: 2,
          questions: [
            new TextboxQuestion({
                ...
            }),
            new DropdownQuestion({
                ...
            })
          ]
        }
      )
    ];
    

    Well, with this changes we have yet how create the formGroup but, how we show the inputs? Before, we are going to change the dinamic-form-component to allow pass as argument the "form"

      @Input() form: FormGroup;
      subGroup:boolean=true;
    
      ngOnInit() {
        if (!this.form)
        {
          this.form = this.qcs.toFormGroup(this.questions);
          this.subGroup=false;
        }
      }
    

    We add a new propertie "subGroup" to indicate if is a subgroup or not. So, we can hide the button "submit".

    At last we are change the dynamic-form-question.component.html to take account the "group-questions"

    <div [formGroup]="form">
      <label [attr.for]="question.key">{{question.label}}</label>
    
      <div [ngSwitch]="question.controlType">
        <input *ngSwitchCase="'textbox'" ...>
        <select *ngSwitchCase="'dropdown'" ...>
        </select>
    
        <div *ngSwitchCase="'group'" [formGroupName]="question.key">
           <app-dynamic-form [form]="form.get(question.key)"
               [questions]="question.questions"></app-dynamic-form>
        </div>
      </div> 
    
      <div  class="errorMessage" *ngIf="!isValid">{{question.label}} is required</div>
    
    </div>
    

    Yes, if we has a group-question, we show an app-dinamic-form pass as form "form.get(question.key)". It's the reason we changed the dinamic-form-component: to allow pass a formGroup and only create a new form if not pass the value.

    In this stackblitz are the full example

    NOTE: Personally I don't like the component create the formGroup. I like create the formGroup in the main.component and pass as argument

    in this other stackblitz explore this idea. The app-component has an ngOnInit that make the two calls

    ngOnInit()
      {
        this.questions = this.service.getQuestions();
        this.form=this.qcs.toFormGroup(this.questions);
      }
    

    And we need give value to the propertie "subGroup" of dinamic-form manually