Home > Software design >  ngFor with dynamic data from WebAPI
ngFor with dynamic data from WebAPI

Time:06-28

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  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]));
              });
        }

CodePudding user response:

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
            
            [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-

CodePudding user response:

Fixed the issue...Its not the issue with creating dynamic form group, its where I declared the formgroup...

I had a child component in this parent component and used @ViewChild decorator for the child component reference. When I declared my formgroup, I placed inbetween these two statement, which I overlooked and that caused all the errors.

@ViewChild('myChild')
public myChildControl: MyChildComponent;

By mistakenly placed my formgroup declaration like below

@ViewChild('myChild')
questionForm: FormGroup =new FormGroup({});
public myChildControl: MyChildComponent;

Moving it below my childcomponent reference fixed the errors.

Thanks Eliseo for your input.

  • Related