12
votes

I am new to angular and trying to combine with easyui angular.

I found this error when trying to open/close dialog:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'klass: l-btn f-inline-row f-content-center l-btn-small l-btn-focus'. Current value: 'klass: l-btn f-inline-row f-content-center l-btn-small'.

And here is my code.

user.component.html

<eui-linkbutton (click)="addAction()" iconCls="icon-add" style="margin-bottom:10px">Add New</eui-linkbutton>
<eui-datagrid 
  [data]="users" style="height1:250px" [filterable]="true" 
  [selectionMode]="'single'"
  [(selection)]="rowSelected"
  [euiContextMenu]="contextMenu"
  >

</eui-datagrid>


<!-- contextmenu -->
<eui-menu #contextMenu (itemClick)="contextClick($event)">
    <eui-menu-item value="edit" text="Edit"></eui-menu-item>
    <eui-menu-item text="Delete"></eui-menu-item>
    <eui-menu-item text="View"></eui-menu-item>
</eui-menu>


<eui-dialog #formDialog [closed]="closed" [draggable]="true" [modal]="true" [title]="isNewRow ? 'Add' : 'Edit'" (close)="closed=true">

  <div class="dialog-button">
    <eui-linkbutton [disabled]="!formObj.valid" (click)="saveAction()" style="width:80px">Save</eui-linkbutton>
    <eui-linkbutton (click)="formDialog.close()" style="width:80px">Cancel</eui-linkbutton>
  </div>
</eui-dialog>

user.component.ts

export class UserComponent implements OnInit {
  rowSelected = null;
  formObj: FormGroup;

  constructor(public userService: UserService, fb: FormBuilder){

    }

  get row(){
      return this.rowSelected;
  }
  set row(value: any){
      this.rowSelected = value;
      this.formObj.reset(this.rowSelected);
  }

  editAction(row){
    this.row = this.rowSelected;
    this.closed = false;
  }

  addAction(){
    console.log('open babe');
    this.row = {
      'id' : null,
      'username' : null,
      'password' : null,
      'email' : null,
      'first_name' : null,
      'last_name' : null
    };

    this.closed = false;
  }


  isNewRow = false;
  editingRow = null;
  closed = true;
  users: Object;

  currentSelection = null;
  ngOnInit() {
    this.userService.getData().subscribe(
      data => this.users = data
    ) 
    console.log('ngoninit');

    this.initRow();
    this.closed = true;
  }

  initRow() {
    this.editingRow = {
      username: null,
      password: null,
      email: null,
      first_name: null,
      last_name: null,
    };
  }

  onAddRow() {
    console.log('open babe');
    this.initRow();
    this.isNewRow = true;
    this.closed = false;
  }

  onEditRow(row) {
    this.isNewRow = false;
    this.editingRow = row;
    this.closed = false;
  }
   contextClick(value){
    if(!this.rowSelected){
      alert('Please select a row first!');
    }
    else{
      if(value == 'edit'){
        this.editAction(this.rowSelected);
      }
    }
  }
}

I have search many tutorial to fix this problem but it seems like the tutorial condition doesn't match with mine.

Do i misunderstand something or missing something practical about angular? what is the proper way to update attribute?

4
this.cdRef.detectChanges(); put it in afterContentInit or ngDocheck Apart from this you would get this error during development Phase not in production cuz angular runs x2 change detection cycles in dev mode - Vikas
@Vikas Agreed with the point that this happens only in dev mode, but this warning or error has to be taken care, else that would lead to inconsistent data binding between the components in production. - Amit Chigadani
@AmitChigadani Yeah I completely agree with you mate:) - Vikas

4 Answers

16
votes

Use ChangeDetectorRef Service to detect new changes

When a view uses the OnPush (checkOnce) change detection strategy, explicitly marks the view as changed so that it can be checked again.

import { Component, Input, ChangeDetectionStrategy,ChangeDetectorRef } from '@angular/core';

@Component({
  selector: 'component',
  templateUrl: 'component.html',
  changeDetection: ChangeDetectionStrategy.OnPush

})


   constructor(cdRef:ChangeDetectorRef){} 
   ngAfterViewInit() {

     this.cdRef.detectChanges();
      }
7
votes

I was getting this error after modifying a Map object that was being iterated over using

*ngFor="let entry of myMap.entries()"

The fix that worked for me was just to add ChangeDetectionStrategy.OnPush to my component declaration:

@Component({
  selector: 'component',
  templateUrl: 'component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
6
votes

Can't able to see the open/close dialog code. Here is explanation for the issue. Usually Angular Updates the DOM based on Model object with help of Change Detection. Angular updates the DOM and it got reflected in UI, After that Model also got updated.

Angular don't know what needs to do, so it's reporting an error. We need to manually trigger the change detection in order to reflect the DOM. Try to wrap the section of code inside setTimeout Block

setTimeout(()=>{

});
3
votes

I believe the issue is you are initializing form for users. So the issue is that the form is building first and then users are getting loaded so angular is detecting changes so it's throwing an error. Ideally this.cdRef.detectChanges(); should work. Also I have added a check in HTML that component will load only after users list are fetched from service.

This might help you :

TS :

     constructor(cdRef:ChangeDetectorRef,public userService: UserService, fb: FormBuilder){
     }

     ngOnInit() {
        this.userService.getData().subscribe(
          data => {
             this.users = data;
             this.initRow();
             this.cdRef.detectChanges();
          }
        ); 
        console.log('ngoninit');
        this.closed = true;
      }

HTML :

 <eui-datagrid *ngIf="users && users.length > 0"
      [data]="users" style="height1:250px" [filterable]="true" 
      [selectionMode]="'single'"
      [(selection)]="rowSelected"
      [euiContextMenu]="contextMenu" >