Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 380
ErnieKev Avatar asked Feb 19 '17 03:02

ErnieKev


1 Answers

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>
like image 176
yurzui Avatar answered Sep 25 '22 13:09

yurzui