Problem binding ngModel to RadioGroup For Dynamic RadioButtons

I’m running into an issue using [(ngModel)] in a custom component with a RadioGroup that has dynamic RadioButton, getting the error:

Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.

If I remove the [(ngModel)] binding, the RadioGroup renders fine but no RadioButtons are checked.

<ion-list radio-group [(ngModel)]="selection">
  <ion-item *ngFor="let option of options">
    <ion-label>{{option}}</ion-label>
    <ion-radio [value]="option"></ion-radio>
  </ion-item>
</ion-list>

I’m setting the options and selection in the ngOnInit method of the component.

Any idea why this is happening?

I’m not sure you’ve given us enough information, because the following works for me:

fruits: string[];
selection: string;

ngOnInit(): void {
  this.fruits = ["apple", "banana", "cherimoya"];
  this.selection = "banana";
}

<ion-list radio-group [(ngModel)]="selection">
  <ion-item *ngFor="let fruit of fruits">
    <ion-label>{{fruit}}</ion-label>
    <ion-radio [value]="fruit"></ion-radio>
  </ion-item>
</ion-list>
1 Like

Hmm, still having the issue. The component does take a formGroup and formControlName as an input, something like:

<div [formGroup]="formGroup">
  <ion-list radio-group [(ngModel)]="selection" [formControlName]="controlName">
    <ion-item *ngFor="let option of options">
      <ion-label>{{option}}</ion-label>
      <ion-radio [value]="option" (ionSelect)="radioChanged($event)"></ion-radio>
    </ion-item>
  </ion-list>
</div>

Could the formGroup or formControlName be part of the problem?

Here is the full stacktrace from the error message:

[17:41:48]  console.error: EXCEPTION: Error in ./ResponseAddPage class ResponseAddPage - inline template:34:78 caused
            by: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'.
[17:41:48]  console.error: ORIGINAL EXCEPTION: Expression has changed after it was checked. Previous value: 'true'.
            Current value: 'false'.
[17:41:48]  console.error: ORIGINAL STACKTRACE:
[17:41:48]  console.error: BaseError@http://localhost:3000/build/main.js:13617:38
            ExpressionChangedAfterItHasBeenCheckedError@http://localhost:3000/build/main.js:61791:20
            checkBinding@http://localhost:3000/build/main.js:29926:115 checkHost detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48
            detectChangesInNestedViews@http://localhost:3000/build/main.js:117846:50 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48
            detectChangesInNestedViews@http://localhost:3000/build/main.js:117846:50 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48
            detectChangesInNestedViews@http://localhost:3000/build/main.js:117846:50 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48
            detectChangesInNestedViews@http://localhost:3000/build/main.js:117846:50 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48
            detectChangesInNestedViews@http://localhost:3000/build/main.js:117846:50 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48 detectChangesInternal
            detectChanges@http://localhost:3000/build/main.js:117661:35
            detectChanges@http://localhost:3000/build/main.js:117754:48
            checkNoChanges@http://localhost:3000/build/main.js:62409:79 forEach@[native code]
            tick@http://localhost:3000/build/main.js:41891:49 http://localhost:3000/build/main.js:41830:109
            onInvoke@http://localhost:3000/build/main.js:44034:43 run@http://localhost:3000/build/polyfills.js:3:6487
            next@http://localhost:3000/build/main.js:41830:84 http://localhost:3000/build/main.js:43234:56
            __tryOrUnsub@http://localhost:3000/build/main.js:13398:20 next@http://localhost:3000/build/main.js:13347:34
            _next@http://localhost:3000/build/main.js:13300:30 next@http://localhost:3000/build/main.js:13264:23
            next@http://localhost:3000/build/main.js:21046:29 emit@http://localhost:3000/build/main.js:43226:80
            checkStable@http://localhost:3000/build/main.js:44002:44
            setHasMicrotask@http://localhost:3000/build/main.js:44073:25
            onHasTask@http://localhost:3000/build/main.js:44046:46
            _updateTaskCount@http://localhost:3000/build/polyfills.js:3:10535
            invokeTask@http://localhost:3000/build/polyfills.js:3:9825
            runTask@http://localhost:3000/build/polyfills.js:3:7093 i@http://localhost:3000/build/polyfills.js:3:3678
            invoke@http://localhost:3000/build/polyfills.js:3:10877 dispatchEvent@[native code]
            handleTapPolyfill@http://localhost:3000/build/main.js:78805:36
            pointerEnd@http://localhost:3000/build/main.js:78715:35 [native code]
            handleTouchEnd@http://localhost:3000/build/main.js:127568:41 [native code]

I still think you’re omitting the actual problem. Still working:

  <div [formGroup]="form">
  <ion-list radio-group [(ngModel)]="selection" formControlName="selection">
    <ion-item *ngFor="let fruit of fruits">
      <ion-label>{{fruit}}</ion-label>
      <ion-radio [value]="fruit"></ion-radio>
    </ion-item>
  </ion-list>
  </div>

  ngOnInit(): void {
    this.selection = "banana";
    this.form = this._fb.group({
      selection: ["banana"]
    });

    this.fruits = ["apple", "banana", "cherimoya"];
  }

I don’t really understand why you have both an ngModel binding and a bound control: I would just do one or the other, but even with both it doesn’t break for me.

1 Like

Ah-ha! Trying to bind it in both the FormControl and ngModel must have been causing the issue, one was setting the checked and the other one changing it.

In your last example, try changing the following.

this.selection = "banana";
this.form = this._fb.group({
   selection: [""]
});

Curious if you get the same error I was experiencing?

Negative, Ghost Rider.

Still works fine that way. If I make selection blank and set the control to “banana” instead, still no error, but now there is no checked selection at startup, so it looks like ngModel trumps the form control if there’s a disagreement.

Any chance your error is being caused by the presence of some event handler (like ionChange, for example)?

Hmm, thats interesting, I thought maybe having conflicting values might be the issue.

Well the good news is, if I pass the value into the FormControl when I’m building the FormGroup, it properly binds to that value without needing to use ngModel in the view.

I was previously only using FormControl to include the Validators, and not passing in an initial value.

Thanks for pointing this out, I’ll need to review my FormGroup and FormControl components, but this definitely helps me out!