Angular 2 Modal Popup Error "Expression has changed after it was checked"
Asked Answered
T

2

6

Youtube video demonstrating the problem

Github repository for the demo app

I have a very simple app with an app component, a child component (account), alert service that handles a message dialog component (popup modal).

For demonstrating purpose, I have two identical forms, one inside app.component.ts and one inside account.component.ts. Each of them has a button that calls the alert service to show the message dialog modal.

The problem is that when I click in the input field of the form for the child component (account.component.ts) and "press enter on my keyboard", I get this error

EXCEPTION: Error in ./AccountComponent class AccountComponent - inline template:2:2 caused by: Expression has changed after it was checked. Previous value: 'true'. Current value: 'false'. Note that this error dose not occur at any other situation mentioned below

  1. If I click the button instead of pressing enter on keyboard

  2. The form from app.componen.ts does not seem to have any issue even when I press enter. It seems to be just the child component (accouunt.component.ts).

  3. If I click the input for account.component, enter something, click the button, no error shown, delete the input, press enter, no error shown now comparing to before

I have look into SO and google and it seems like people are having the same issue and resolving it by calling change detect. However, I have tried that and put it in places such as just after modal is shown and it didn't work. Also, if that would solve it, then it doesn't explain why the form in app.component.ts does not cause me this error.

Below are some code snippets, the full demo project can be found on the github link above. This problem has be troubling me for days. Much appreciated for the help.

app.component.html

<label>This form is from app.component.html</label>
<form name="form" [formGroup]="changePasswordForm" (ngSubmit)="onUpdatePassword()">
    <input placeholder="Old Password" formControlName="oldPassword">
    <button class="btn btn-success">Update Password</button>
</form>

<br><br><br><br>

<label>This form is from account.component.html</label>
<router-outlet> </router-outlet>

<template ngbModalContainer></template>

app.component.ts

export class AppComponent implements OnInit {

    private changePasswordForm: FormGroup;

    constructor(
      private formBuilder: FormBuilder,
      private alertService: AlertService,
    ) { }

    ngOnInit() {
      this.changePasswordForm = this.formBuilder.group({
        oldPassword: [''],
      })
    }

    onUpdatePassword() {
      this.alertService.alertPopup('test2', 'asfafa')
    }
}

account.component.html

<form name="form" [formGroup]="changePasswordForm" (ngSubmit)="onUpdatePassword()">
  <input placeholder="Old Password" formControlName="oldPassword">
  <button class="btn btn-success">Update Password</button>
</form>

account.component.ts

export class AccountComponent implements OnInit {

  private changePasswordForm: FormGroup;

  constructor(
    private formBuilder: FormBuilder,
    private alertService: AlertService,
  ) { }

  ngOnInit() {
    this.changePasswordForm = this.formBuilder.group({
      oldPassword: [''],
    })
  }

  onUpdatePassword() {
    this.alertService.alertPopup('test2', 'asfafa')
  }
}

alert.service.ts

@Injectable()
export class AlertService {
    private subject = new Subject<any>();
    private keepAfterNavigationChange = false;

    constructor(
        private router: Router,
        private modalService: NgbModal,
    ) { }


    alertPopup(title: string, content: string) {
        // open modal to check if worked over night
        const modalRef = this.modalService.open(MessageDialogComponent);

        modalRef.componentInstance.titleText = title
        modalRef.componentInstance.bodyText = content

        modalRef.result
            .then(response => {
            })
            .catch(() => {
                return
            })
    }
}

message-dialog.component.html

<div class="modal-header">
  <h4 class="modal-title">{{titleText}}</h4>
</div>

<div class="modal-body">
  <p>{{bodyText}}</p>
</div>

message-dialog.component.ts

export class MessageDialogComponent implements OnInit {

  @Input() titleText: string;
  @Input() bodyText: string;

  constructor(
    public activeModal: NgbActiveModal,
  ) { }

  ngOnInit() {
  }

}

Screen shot

Tungus answered 19/2, 2017 at 3:48 Comment(0)
P
11

Seems your error occurs after execution following code:

ngAfterViewInit() {
    if (!this._elRef.nativeElement.contains(document.activeElement)) {
      this._renderer.invokeElementMethod(this._elRef.nativeElement, 'focus', []);
    }
}

https://github.com/ng-bootstrap/ng-bootstrap/blob/1.0.0-alpha.20/src/modal/modal-window.ts#L65

on input is fired blur event that marks your control as touched.

It doesn't work for AccountComponent because detection changes in AccountComponent occurs before ngbModalContainer while FormGroup within app.component.html gets right values.

Possible solutions:

1) mark your controls as touched before opening modal

account.component.ts

onUpdatePassword() {
  Object.keys(this.changePasswordForm.controls).forEach(key => {
     this.changePasswordForm.controls[key].markAsTouched();
  });

  this.alertService.alertPopup('test2', 'asfafa')
}

2) change order tags

app.component.html

<template ngbModalContainer></template>
<router-outlet> </router-outlet>
Potty answered 19/2, 2017 at 7:34 Comment(4)
Thanks so much! Easy fix with option 2!. May I ask you did find out what the problem was as the error code was not too meaningful as to where the problem was?Tungus
Honestly, it's very difficult to debug such errors. You need to know how change detection works and how angular2 generates factory for each componentPotty
But you did it so quickly though? I have been stuck for days. Thanks!Tungus
Is there a way to step through the code to find out which function was causing the problem? Seems like you tracked down the exact location of the cause. I was sitting there thinking where did this true and false came fromTungus
M
6

I even gone through same error once,

Use ngAfterViewInit() in your appComponent instead ngOnInit.

If that didn't solve just try

setTimeOut(function(){
//Your Code
},1)
Maybellemayberry answered 19/2, 2017 at 6:57 Comment(4)
the only code inside my app.component.ts ngOnInit is this.changePasswordForm = this.formBuilder.group.... And putting it inside ngAfterViewInit will cause the formGroup expects a FormGroup instance Error. Same goes for setTimeout function.Tungus
it could be better if you have your code on plnkr, Ok I'll get from github and check.Maybellemayberry
Thanks so much mate. I have been struggling with this one. Im not sure how to set this up in plunker as it involves angular material 2 and bootstrap class to get the modal working.Tungus
Hey mate. Thanks so much for looking into it. the other solution worked so everything is good now. I just changed the template to before router-outletTungus

© 2022 - 2024 — McMap. All rights reserved.