swimlane / ngx-datatable

✨ A feature-rich yet lightweight data-table crafted for Angular
http://swimlane.github.io/ngx-datatable/
MIT License
4.63k stars 1.68k forks source link

ExpressionChangedAfterItHasBeenCheckedError #721

Open BotanMan opened 7 years ago

BotanMan commented 7 years ago

[x] bug report => search github for a similar issue or PR before submitting [ ] feature request [ ] support request => Please do not submit support request here, post on Stackoverflow or Gitter

Current behavior It's looks like it relates to other library, but it's interferes with ngx-datatable, at least I reproduced in that connection

When I show modal window from ng-bootstrap by handling (select) output I get ng:///NgxDatatableModule/DataTableBodyRowComponent.ngfactory.js:12 ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'datatable-body-cell sort-active active'. Current value: 'datatable-body-cell sort-active'.

it works fine when I show the same modal by (click) on some button on the page, but it raise each time I do that by (select), and when it happens, next modal activate break the page

Reproducing Fully reproduced with last angular and both packages here http://plnkr.co/edit/Ru8xoU3PNOqu4S1qUfbn?p=preview

BotanMan commented 7 years ago

Intersting, but in case I put some button in column

 <ngx-datatable-column name="Registration Date"
                                prop="registrationDate"
                                [resizeable]="false"
                                [flexGrow]="1">
            <ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
              <button
                  class="btn-primary btn"
                  (click)="emptyClickHandler()">Edit</button>
            </ng-template>
          </ngx-datatable-column>

So I have both (activate)="click()" as table component input and (click)="emptyClickHandler()" as button (click) input

there are no error.

dae721 commented 7 years ago

Getting the same error in a similar situation: instead of bringing up the dialog when the row is selected, I have a column that shows an icon (just a span) which has a click event handler to bring up the ng-bootstrap dialog. If I put the click handler on a button instead of the icon, it works.

andrii-oleksyshyn commented 7 years ago

Hello, do we have any updates regarding this issue?

faxemaxee commented 7 years ago

Exact same situation as @BotanMan . Trying to open BootstrapModal with dyn. component only that I'm using (active) not (select) to fire the modal. But exact same Error.

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'datatable-body-cell sort-active active'. Current value: 'datatable-body-cell sort-active'.

any news? @amcdnl ?

himanshu-khurana commented 7 years ago

Same issue I am facing that when i click on my icon in ngx-datatable column open modal it works fine but showing same error in my console.

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'datatable-body-cell sort-active active'. Current value: 'datatable-body-cell sort-active'.

Do we have any updates regarding this issue?

SnakeB commented 7 years ago

Any updates to this issue i am getting the same error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'datatable-body-cell sort-active active'. Current value: 'datatable-body-cell sort-active'.

maxtacco commented 7 years ago

Is it related to https://github.com/angular/angular/issues/17572 by any chance?

himanshu-khurana commented 7 years ago

I am still facing the issue. If i got a solution i comment on git hub in this topic

On Wed, Aug 2, 2017 at 3:47 PM, Max notifications@github.com wrote:

Is it related to angular/angular#17572 https://github.com/angular/angular/issues/17572 by any chance?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/swimlane/ngx-datatable/issues/721#issuecomment-319631345, or mute the thread https://github.com/notifications/unsubscribe-auth/AO5u-SfZkasULbqWGBE_N8mGFySLstN2ks5sUEyygaJpZM4NLbKd .

-- Thanks & Regards

[image: Oodles Technologies Pvt Ltd] http://www.oodlestechnologies.com/

Himanshu Khurana Assistant Consultant-UI, Oodles Technologies Pvt Ltd Phone: +91 124 4368 395 Mobile: +91 9899442240 Email: himanshu.khurana@oodlestechnologies.com maneesh@oodlestechnologies.com Website: http://www.oodlestechnologies.com/ Skype: himanshukhurana.oodles

[image: Twitter] https://twitter.com/oodlestech [image: Facebook] https://www.facebook.com/OodlesTech [image: Google +] https://plus.google.com/+Oodlestechnologies/posts [image: LinkedIn] https://www.linkedin.com/company/oodles-technologies-pvt-ltd

torontocode commented 7 years ago

I'm not very proud of this solution, but the only way I got it to work is by blurring the datatable-body-cell right before emptyClickHandler main logic. We can target it by passing a reference to the template element and then looking up the parent of the parent (btnElement.parentElement.parentElement)

Something like this:

HTML:

<ngx-datatable-column name="Registration Date"
                         prop="registrationDate"
                         [resizeable]="false"
                         [flexGrow]="1">
       <ng-template let-row="row" let-value="value" ngx-datatable-cell-template>
       <button #btnElement
             class="btn-primary btn"
             (click)="emptyClickHandler(btnElement)">Edit</button>
       </ng-template>
</ngx-datatable-column>

JS:

emptyClickHandler(btnElement) {
     btnElement && btnElement.parentElement && btnElement.parentElement.parentElement &&
         btnElement.parentElement.parentElement.blur();

// ... Other logic here .. say to open a modal window

}
maxisam commented 7 years ago

I think this error is causing by the cell is deactive by popping up a modal, since it loses focus. The problem is the table component has been checked by angular and now it is changed by the modal. Therefore, it throws an error like ExpressionChangedAfterItHasBeenCheckedError.

To prevent this error, I think the best way is preventing the cell become active or as soon as it becomes active, deactivate it.

onActive(event) {
        (event.type === 'click') && event.cellElement.blur();
}
torontocode commented 7 years ago

I couldn't get this to work @maxisam. Your solution relies on event bubbling which makes its reponse too late - the error has already happened. Shame because I thought it looked like a more elegant solution.

maxisam commented 7 years ago

@torontocode http://plnkr.co/edit/y2rPVs32V09nSEuffD3W?p=preview

ScottSpittle commented 7 years ago

For the timebeing I wrapped the code at which this occurs in a setTimeout.

setTimeout(() => {
    //Code that triggers the error
})
zoubarevm commented 7 years ago

Our case called for something a little different (because we needed to call event.stopPropagation() to prevent selection (onSelect) of the row (so @torontocode 's solution worked).

component.ts

...
public deleteField(event: any, row: any, firstChild: any): void {
    event.stopPropagation();
    firstChild && firstChild.parentElement && firstChild.parentElement.parentElement &&
      firstChild.parentElement.parentElement.blur();
    // open delete modal
    this.modalService.open(DeleteComponent);
    ...
}

public onSelect($event): void {
    // navigate to details page
}
...

component.html

<ngx-datatable-column>
  <ng-template ngx-datatable-cell-template let-row="row">
    <div #firstChild>
      <div (click)="deleteField($event, row, firstChild)" class="field-delete text-center">
        <i class="fa fa-trash"></i>
      </div>
    </div>
  </ng-template>
</ngx-datatable-column>

Does anyone know if there's a better way to do this so that we could use the onActive method (bypassing the onSelect) ?

maxtacco commented 7 years ago

As @maxisam noted, calling event.cellElement.blur() from (activate) works for me.

component.html

<ngx-datatable
    #datatable
    class="custom"
    [columns]="columns"
    [rows]="rows"
    [selected]="selected"
    [loadingIndicator]="loadingIndicator"
    [columnMode]="'force'"
    [headerHeight]="40"
    [footerHeight]="0"
    [rowHeight]="'auto'"
    [reorderable]="false"
    [sortType]="'single'"
    [selectionType]="'single'"
    [selectCheck]="singleSelectCheck"
    (activate)="onActivate($event)">
</ngx-datatable>

component.ts

onActivate(event: any) {
    if (event.type === 'dblclick') {
        event.cellElement.blur();
        this.edit(event); 
    }
}
bweeres commented 7 years ago

Same issue. I had to use both the onActivate from @maxtacco and @maxisam and the setTimeout from @ScottSpittle to get rid of the error. Neither worked for me by themselves.

hvqthong commented 6 years ago

Call event.cellElement.blur(); when click worked for me 👍

paustint commented 6 years ago

I am reporting that I am also experiencing this issue.

using I have cells that sometimes have a button that opens a model using ngBootstrap to show some data and this change detection error occurs each time the modal opens.

@maxisam - I was able to clear the error with your suggestion, thank you! Hopefully there will be some fix for this in the core library and I can remove this hack or find a better way around this without having to subscribe to activate(), as I do not need it for any other prupose

insidewhy commented 6 years ago

Error happens for us, it indicates that ngx-datatable is violating Angular's one way data flow. In development mode and non-AOT production builds luckily the only bad effect is the warning. With an AOT compiled build you get data synchronocity issues 😒

needforspeed commented 6 years ago

@maxtacco is this event only work for active? why the click event doesn't have the cellElement property?

maxtacco commented 6 years ago

@needforspeed I needed that for double click, but as @maxisam showed it should work for single click as well.

xyrintech commented 6 years ago

Anyone managed to fix this?

xyrintech commented 6 years ago

No solution seems to be working.

insidewhy commented 6 years ago

@xyrintech you could wrap ngx-datatables in an ng-container and use an *ngIf="...; let ..." over your stream. That's what I did anyway. Works around the bug.

xyrintech commented 6 years ago

I understand, may I have the sample code for this?

insidewhy commented 6 years ago

<ng-container *ngIf="stream$ | async; let value"> then use the resolved value instead of stream$. No empty stream no troubles.

anx-ckreuzberger commented 6 years ago

Any updates on this? We are running into the same issue...

swseverance commented 6 years ago

I encountered this same issue today

jessicahur commented 6 years ago

I also encounter this problem today. I had to implement the workaround as others have mentioned above. My template:

                              prop="job_id"
                              [width]="80"
                              [sortable]="false">
            <ng-template let-row="row" ngx-datatable-cell-template>
                <span class="btn-link fake-link" (click)="getTranLog(row.job_id, $event)">{{row.job_id}}</span>
            </ng-template>
        </ngx-datatable-column>

And in my component:


        event.target.parentElement.parentElement.blur();
        this.modalRef                         = this._modalSvc.open(TranLogModalComponent);
        this.modalRef.componentInstance.jobID = jobID;
    }``` 
XciD commented 6 years ago

Same problem, resolved with blur...

event.target.closest('datatable-body-cell').blur();
macpatel commented 6 years ago

I had to use both the onActivate from @maxtacco and @maxisam and the setTimeout from @ScottSpittle to get rid of the error. Neither worked for me by themselves.

Hoorayyy!

petr-like commented 5 years ago

i fixed this error, added tag button for div

button.action__group-delete.action__group-btn(
   [inlineSVG]="'/assets/components/collections/archive/delete.svg'",
   (click)="delete(row.id)"
)
.action__group-delete.action__group-btn(
   [inlineSVG]="'/assets/components/collections/archive/delete.svg'",
   (click)="delete(row.id)"
)

without button we haved error on console

Previous value: 'className: datatable-body-cell archived__date sort-active active'. Current value: 'className: datatable-body-cell archived__date sort-active'

sacgrover commented 4 years ago

Use (mousedown) instead of (click) event. It's worked for me. It's not a proper solution but It's a workaround for now.

nathangs commented 4 years ago

I ran into this same issue and was able to solve by using a button element instead of a link element. Links seems to work fine when routing to another page but if staying on the same page I've found the need to use buttons.

  <a [routerLink]="[ './', value ]" class="pl-3">
    <span class="fa fa-pencil">
    </span>
  </a>
  <!-- must be a button or throws ExpressionChangedAfterItHasBeenCheckedError! -->
  <button type="button" class="pl-3" (click)="delete(value);">
    <span class="fa fa-trash"></span>
  </button>

(update: fixed code formatting)

rameshdhungana commented 4 years ago

I faced same problem today. I tried all the of the recommend solutions. The one that worked and most easy to implement was solution suggested by @jessicahur .Thanks jessi for the easy pick up solution. This is how i implemented in my project.

In html component,
<i title='View City Detail' class="add-icon fa fa-eye" (click)=viewCityDetail($event,row)>

In ts component,

viewCityDetail(event, seletedCity) { console.log(event, seletedCity) event.target.parentElement.parentElement.blur(); const modalRef = this.modalService.open(CityDetailComponent, { size: 'lg' }); modalRef.componentInstance.user = { seletedCity: seletedCity };

}

andrei-gheorghiu commented 3 years ago

Not sure if it's a cleaner solution, but it definitely works:

import { ɵmarkDirty as markDirty } from '@angular/core';

// and, at the very end of your event handler fn:
markDirty(this);
shrutikabongarde commented 3 years ago

I also encounter this problem today. I had to implement the workaround as others have mentioned above. My template:

                              prop="job_id"
                              [width]="80"
                              [sortable]="false">
            <ng-template let-row="row" ngx-datatable-cell-template>
                <span class="btn-link fake-link" (click)="getTranLog(row.job_id, $event)">{{row.job_id}}</span>
            </ng-template>
        </ngx-datatable-column>

And in my component:

      event.target.parentElement.parentElement.blur();
      this.modalRef                         = this._modalSvc.open(TranLogModalComponent);
      this.modalRef.componentInstance.jobID = jobID;
  }``` 

its work

ofarril870616 commented 3 years ago

Hello, I fixed using button. I changed my <i class="ft-edit text-primary cursor-pointer" (click)="open(content, 'edit')" > for <button class="ft-edit text-primary cursor-pointer" (click)="open(content, 'edit')">

StrydsNageshP commented 3 years ago

Same issue I am facing that when i click on my icon in ngx-datatable column open modal it works fine but showing same error in my console.

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'datatable-body-cell sort-active active'. Current value: 'datatable-body-cell sort-active'.

Do we have any updates regarding this issue?

event.target.closest('datatable-body-cell').blur(); Add this line on your function

essaadiMehdi commented 3 years ago

I solved the problem by changing the code <a class="btn btn-icon h-auto tblViewBtn" (click)="view(row.interventionId)"> To : <button class="btn btn-icon h-auto tblViewBtn" (click)="view(row.interventionId)">

pravinmitta commented 2 years ago

I also encounter this problem today. I had to implement the workaround as others have mentioned above. My template:

                              prop="job_id"
                              [width]="80"
                              [sortable]="false">
            <ng-template let-row="row" ngx-datatable-cell-template>
                <span class="btn-link fake-link" (click)="getTranLog(row.job_id, $event)">{{row.job_id}}</span>
            </ng-template>
        </ngx-datatable-column>

And in my component:

      event.target.parentElement.parentElement.blur();
      this.modalRef                         = this._modalSvc.open(TranLogModalComponent);
      this.modalRef.componentInstance.jobID = jobID;
  }``` 

It's Worked for me

alebx commented 1 year ago

I'm having the same problem but with the header template.

(click)=$event.currentTarget.closest('datatable-header-cell').blur() doesn't work.

ERROR Error: NG0100: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'false'.. Find more at https://angular.io/errors/NG0100
    at throwErrorIfNoChangesMode (core.mjs:6744:1)
    at bindingUpdated (core.mjs:12747:1)
    at ɵɵproperty (core.mjs:14480:1)
    at DataTableHeaderComponent_div_1_datatable_header_cell_1_Template (swimlane-ngx-datatable.js:108:22)
    at executeTemplate (core.mjs:9632:1)
    at refreshView (core.mjs:9495:1)
    at refreshEmbeddedViews (core.mjs:10646:1)
    at refreshView (core.mjs:9519:1)
    at refreshEmbeddedViews (core.mjs:10646:1)
    at refreshView (core.mjs:9519:1)