1
votes

I'm having a problem where when the very last element in my form has a value bound to it the error "Expression has changed after it was checked." is thrown.

I will preface by saying this is based off of the Angular 2 website example here - https://angular.io/docs/ts/latest/cookbook/dynamic-form.html#!#top

The way my app works is first I build a dynamic form with controls in my form component based off a model.

My form components html loops the questions in the model like so

<form *ngIf="showForm" [formGroup]="formGroup">
    <!-- questions-->
    <div *ngIf="questions.length > 0">
        <div *ngFor="let question of questions">
            <question [question]="question" [formGroup]="formGroup"></question>
        </div>
    </div>
    <button pButton type="submit" label="Submit" icon="fa-check-circle-o" iconPos="left"
            [disabled]="!formGroup.valid" (click)="submitFinalForm()"></button>
</form>

Below is the question component html that uses the data that was passed in from the form component to display certain types of questions via ngSwitch

<label [attr.for]="question.field">
    {{ question.question }}
</label>
<div [ngSwitch]="question.type">
    <!-- Radio / Checkbox -->
    <radio-checkbox-question *ngSwitchCase="1" [formGroup]="formGroup" [question]="question"></radio-checkbox-question>
</div>

Finally here is the radio-checkbox-question component

<div *ngIf="showQuestion" [formGroup]="formGroup">
    <!-- Radio -->
    <div *ngIf="model.radiocheckbox == 'radio'">
        <div *ngFor="let label of model.labels; let i = index;">
             <p-radioButton name="{{model.field}}"
                            value="{{i}}"
                            label="{{label}}"
                            formControlName="{{model.field}}"
                            [(ngModel)]="questionAnswerRadio"></p-radioButton>
        </div>
    </div>
</div>

Here is the actual component TS

import { Component, Input, OnInit }         from "@angular/core";
import { FormGroup }                        from "@angular/forms";

import { RadioCheckboxQuestion }            from "../Questions/radio.checkbox.question.model";

@Component({
    selector: "radio-checkbox-question",
    templateUrl: "radio.checkbox.component.html"
})
export class RadioCheckboxComponent implements OnInit {

    @Input() question: any;
    @Input() formGroup: FormGroup;

    model: RadioCheckboxQuestion = new RadioCheckboxQuestion();
    showQuestion: boolean = false;

    questionAnswerRadio: string = "";

    ngOnInit(): void {
        // question essential properties
        if (this.question.hasOwnProperty("field") && this.question["field"] &&
            this.question.hasOwnProperty("labels") && this.question["labels"]) {
            this.model.field = this.question["field"];
            this.model.labels = this.question["labels"];

            // assume always radio for debugging
            this.model.radiocheckbox = "radio";

            // set existing answer
            if (this.question.hasOwnProperty("QuestionAnswer") && this.question["QuestionAnswer"]) {
                if (this.model.radiocheckbox == "radio") {
                    this.questionAnswerRadio = this.question["QuestionAnswer"];
                }
            }

            this.showQuestion = true;
        }
    }
}

I've also seen many SO issues like the following Angular 2 dynamic forms example with ngmodel results in "expression has changed after it was checked" which basically state that [(ngModel)] should not be used with dynamic forms, but the primeNG documentation says the components can work with model driven forms and the only way to set the answer (that I know of) is [(ngModel)]. I believe what might happen here is since I set the only question in the formGroup to a value that the formGroup becomes valid in between the change detection and causes the error

Error in ./FormComponent class FormComponent - inline template:17:48 caused by: Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.

2
Can you also create plunker?yurzui
I'm not sure how to make a plunker of the latest Angular 2 and PrimeNG versions it seems like they are outdated? Something strange is that in my form component if I import ChangeDetectionStrategy, ChangeDetectorRef and use changeDetection: ChangeDetectionStrategy.OnPush along with private cd: ChangeDetectorRef and this.cd.markForCheck(); to manually update my form component the issue then no longer happens, but in the complete version of the form the text box inputs from PrimeNG are populated and the radio/checkbox buttons are not.user3333134

2 Answers

1
votes

From your template it looks like you are using both model drive (formControlName) and template driven (ngModel).

 <p-radioButton name="{{model.field}}"
                            value="{{i}}"
                            label="{{label}}"
                            formControlName="{{model.field}}"
                            [(ngModel)]="questionAnswerRadio"></p-
   <radioButton>

Please select one way and try again. I suggest you to remove the [(ngModel)]

0
votes

The only way i've found to get the change detection to be happy with my multi-nested components and primeNG was to implement full change detection manually. What that basically means was in every component I had to add something like the following

import ChangeDetectorRef

constructor(private change: ChangeDetectorRef)
{}

ngOnInit() {

    // code here that inits everything
    this.change.markForCheck();
}

Anything less then this caused the change detection errors to pop-up in different and unique ways in the components that used primeNG.