Search code examples
angularformbuilderdynamic-forms

Making a dynamic form in angular, add fields on runtime


I am making a site where I can create exams with multiple choice questions. I have a html page in which I can add questions to an exam.

A question consists out of the description of the question and multiple options. An option contains the response and a value to check if the response is true or false.

The user should be able to add options to the question when filling in the form. I am trying to use formbuilder, formgroup and formarray for this but it is not working. When I press add option, a new response and a new valid field should appear.

form

The fields are appearing on my html page, but I get this error when filling in the form. "Cannot find control with name response" +"cannot find control with name valid"

This is the HTML page:

    <form [formGroup]="questionForm" (ngSubmit)="onSubmit(questionForm.value)">
        <div>
          <label>Description:</label>
          <br />
          <input class="form-control" id="description" type="text" formControlName="description">
          <br />
          <label>Options</label>

          <div *ngFor="let test of arrayOptions; let i = index">
            <div class=mx-4>
              <label>Response</label>
              <input class="form-control" id="response" type="text" formControlName="response">
              <label>Valid</label>
              <input class="form-control" id="valid" type="text" formControlName="valid">
            </div>


          </div>

          <button type="button" class="btn-block-option text-primary" (click)="addOption()">
            <i class="fa fa-plus" aria-hidden="true"></i>Add option
          </button>


        </div>
        <div class="block-content block-content-full block-content-sm bg-body-light font-size-sm">
          <button class="btn btn-primary" type="submit">Add</button>
        </div>
      </form>

This is my ts page:

    export class AddQuestionComponent implements OnInit {

      questionForm;
      arrayOptions;

      constructor(
        private examSimulatorService: ExamSimulatorService,
        private formBuilder: FormBuilder
      ) {
        this.questionForm = this.formBuilder.group({
          description: '',
          options: ''
        })
      }
      removeOption(i) {
        this.questionForm.removeControl(i);
      }
      addOption() {

    this.questionForm.addControl(this.questionForm.controls.options.value, 
    this.formBuilder.array([this.formBuilder.group(
          {
            response: "",
            valid: ""
          }
        )]));
        this.arrayOptions.push(this.questionForm.controls.options.value);
      } 

      onSubmit(questionData) {
        this.examSimulatorService.addQuestion(questionData);
        this.questionForm.reset();
      }

      ngOnInit() {
        this.arrayOptions = [];
        this.addOption();
      }

    }

Solution

  • Hard to tell the exact errors without a running sample.

    Some things I have noticed:

    1) When we are using a FormArray we have to bind it in the template via formArrayName

    2) I don't really get why you are calling addControl on the questionForm. Should'nt we push a new option to the FormArray?

    Example form with a FormArray:

    this.questionForm = this.fb.group({
      question: ["Sample Question"],
      options: this.fb.array([
        this.fb.group({
          response: ["Response Example"],
          valid: [true]
        })
      ])
    });
    

    Referencing the FormArray in the template via formArrayName:

    <form [formGroup]="questionForm">
      <input type="text" formControlName="question" />
    
      <div formArrayName="options">
    
        <div *ngFor="let option of options.controls; let i=index">
          <b>Option {{i}}:</b>
          <div [formGroupName]="i">
              <input type="text" formControlName="response" />
              <input type="checkbox" formControlName="valid" />
          </div>
        </div>
    
    </div>
    </form>
    

    Finally the method for adding a new option:

    addOption() {
      this.options.push(
        this.fb.group({
          response: [""],
          valid: [false]
        })
      );
    }
    
    // getter which simplifies the access via 'this.options' used in the addOption method
    get options() {
      return this.myForm.get("options") as FormArray;
    }
    

    Take a look at the codesandbox. Feel free to fork and edit it so that it shows your problem better.