Search code examples
angularvalidationdynamicngforformgroups

ngFor with dynamic data from WebAPI


I have a list of questions loaded from WebAPI. Based on the questionType, the answer is either radio options/text. Now I want to validate my answers(required/custom validator). I added formcontrol to formgroup dynamically after the webAPI returns the list of questions.

Error - NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: '[object Object]'. Current value: 'undefined'..

If *ngIf="questionForm" is removed from html, then Error - formGroup expects a FormGroup instance. Please pass one in.

To my understanding, the formgroup instance is created and it gets reloaded as undefined again somewhere in the angular lifecycle.

.html

<form *ngIf="questionForm" [formGroup]="questionForm">
 <ng-container *ngFor="let item of Questions;let i = index;">
        <mat-label >{{item.question}}</mat-label>
        <div [ngSwitch]="item.questionType">
            <div *ngSwitchCase="'bool'">
               <mat-radio-group class="radio__button" aria- 
               label="Select an option">
               <mat-radio-button id="YesAnswer_{{item.id}}" 
                #Answer_{{item.id}}
                name="Answer_{{item.id}}" value="1" 
                formControlName="Answer_{{item.id}}">Yes</mat-radio- 
                button>
               <mat-radio-button id="NoAnswer_{{item.id}}" 
                 #Answer_{{item.id}}
                 name="Answer_{{item.id}}" value="0" 
                 formControlName="Answer_{{item.id}}">No</mat-radio- 
                button>
               </mat-radio-group>
             </div>
             <div *ngSwitchCase="'string'">
                  <mat-form-field appearance="outline">
                   <input matInput type="text" 
                    id="Answer_{{item.id}}" #Answer_{{item.id}}
                     formControlName="Answer_{{item.id}}" 
                     name="Answer_{{item.id}}">
                   </mat-form-field>
              </div>
      </div>
    </ng-container>
</form>

.ts file ngOnInit() method. this.Questions holds the list of questions from WebAPI and I made sure that the below code runs only after the Questions are loaded.Adding a console.log(this.questionForm); below this statement shows the list of controls in the FormGroup.

 ngOnInit():void{
    this.service.GetQuestion().subscribe({
          error: (err) => { console.log(err); },
          next: (data) => {
            if (data) {
              this.Questions = data as Questions[];
        this.questionForm = new FormGroup ({});
        this.Questions.forEach(quest=>{
              this.questionForm.addControl('Answer_'+String(quest.id),new FormControl( '', 
              [Validators.required]));
              });
        }

Solution

  • Try to rename the name of the Interface Questionsto Question

    Well, I want to say your that when you use a mat-radio-group, it's this where you use formControlName. BTW I don't like so much the use of interpolation, use binding and you has wrong positioned the label.

    This is my .html

    <form *ngIf="questionForm" [formGroup]="questionForm">
      <ng-container *ngFor="let item of Questions; let i = index">
        <div [ngSwitch]="item.questionType">
          <div *ngSwitchCase="'bool'">
            <mat-label>{{ item.question }}</mat-label>
            <div>
              <mat-radio-group
                class="radio__button"
                [formControlName]="'Answer_' + item.id"
                aria-label="Select an option"
              >
                <mat-radio-button value="1">Yes</mat-radio-button>
                <mat-radio-button value="0">No</mat-radio-button>
              </mat-radio-group>
            </div>
          </div>
          <div *ngSwitchCase="'string'">
            <mat-form-field appearance="outline">
              <mat-label>{{ item.question }}</mat-label>
              <input matInput type="text" [formControlName]="'Answer_' + item.id" />
            </mat-form-field>
          </div>
        </div>
      </ng-container>
    </form>
    

    And a stackblitz that works (and is your code -well, I change the interface Questions by Question)

    BTW, when you use a FormArray of FormControls you can use validator only in the FormControls you need -not in the whole FormArray-