2
votes

In my Angular 4 project I want to display one of my 2 table based on the value of a toggle button, if false I show first table, if true I show second table.

I am using primeng datatable inside a div like this:

<div *ngIf="checked">
  <p-dataTable [value]="models" [rows]="10"[paginator]="true"....
</div>

And the toggle is

    <mat-slide-toggle labelPosition="before" [(ngModel)]="checked">
{{first table }}</mat-slide-toggle>

But when I click the toggle button I have :

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

So how can I avoid this error?

This is the entire component:

<div class="main-content">
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-12">
                <div class="card">
                    <div class="card-header card-header-icon"
                        data-background-color="main-theme-color">
                        <i class="material-icons">euro_symbol</i>
                    </div>
                    <div class="card-content">
                        <h4 class="card-title">{{'movement.table.titles.header' |
                            translate }}</h4>
                        <div class="row">
                            <div class="col-md-2 col-md-offset-10">
                                <!-- matTooltip="Like" [matTooltipPosition]="'left'"
                                title="{{'movement.table.tooltips.add' | translate }}"-->
                                <button type="button" class="btn btn-info btn-round pull-right"
                                    (click)="routeToView(['/movements/new'])"
                                    matTooltip="{{'movement.table.tooltips.add' | translate }}"
                                    [matTooltipPosition]="'above'"
                                    [matTooltipShowDelay]="tooltipShowDelay"
                                    [matTooltipHideDelay]="tooltipHideDelay">
                                    <span class="btn-label"> <i class="material-icons">add</i>
                                    </span>
                                </button>
                            </div>
                        </div>

                        <div class="row">
                            <div class="col-md-2">
                                <mat-slide-toggle labelPosition="before" [(ngModel)]="checked">{{'movement.table.toggle.competenceview'
                                | translate }}</mat-slide-toggle>
                            </div>
                            <div class="col-md-2">
                                <label class="pull-right">{{'movement.table.toggle.financialview'
                                    | translate }}</label>
                            </div>
                        </div>

                        <div class="col-md-12" *ngIf="checked">
                            <div class="content table-responsive">
                                <p-dataTable #dt [value]="models" [rows]="defaultPageSize"
                                    [paginator]="true" [rowsPerPageOptions]="pageSizeOptions"
                                    [responsive]="true" resizableColumns="true" [lazy]="true"
                                    [totalRecords]="totalElements" sortField="createdDate"
                                    [sortOrder]="-1" (onLazyLoad)="loadDataIntoTable($event)"
                                    [loading]="loader" reorderableColumns="true"> <p-column
                                    field="createdDate" [sortable]="true"
                                    header="{{'movement.table.headers.date' | translate }}">
                                <ng-template let-col let-date="rowData" pTemplate="body">
                                <span> {{date[col.field] | formatdate | date
                                    :('pattern.datehourmin' | translate)}} </span> </ng-template></p-column> <p-column
                                    field="currencyDate" [sortable]="true"
                                    header="{{'movement.table.headers.currencydate' | translate }}">
                                <ng-template let-col let-date="rowData" pTemplate="body">
                                <span> {{date[col.field] | formatdate | date
                                    :('pattern.date' | translate)}} </span> </ng-template></p-column> <p-column field="description"
                                    header="{{'movement.table.headers.description' |
                            translate }}"
                                    [sortable]="true"> <ng-template
                                    let-movement="rowData" pTemplate="body"> <span
                                    *ngIf="movement.refundId || movement.ticketBundleId"
                                    class="crosslink" (click)="selectDescription(movement)">{{movement.description}}</span>
                                <span
                                    *ngIf="movement.refundId === undefined && movement.ticketBundleId === undefined">{{movement.description}}</span>
                                </ng-template> </p-column> <p-column field="paymentTypeName"
                                    header="{{'movement.table.headers.paymenttypename' |
                            translate }}"
                                    [sortable]="true"></p-column> <p-column field="amount"
                                    header="{{'movement.table.headers.amount' | translate }}"
                                    [sortable]="true"> <ng-template let-col
                                    let-amount="rowData" pTemplate="body"> <span
                                    [style.color]="amount[col.field] < 0 ? 'red' : 'green'">
                                    {{amount[col.field] | currency:'EUR':true}} </span> </ng-template></p-column> <p-column
                                    styleClass="col-button"> <ng-template
                                    let-model="rowData" pTemplate="body">

                                <div class="text-center">
                                    <span><button
                                            class="btn btn-simple btn-success btn-icon edit"
                                            (click)="selectModel(model)"
                                            matTooltip="{{'movement.table.tooltips.view' | translate }}"
                                            [matTooltipPosition]="'left'"
                                            [matTooltipShowDelay]="tooltipShowDelay"
                                            [matTooltipHideDelay]="tooltipHideDelay">
                                            <i class="material-icons">dvr</i>
                                        </button> </span>
                                </div>
                                </ng-template> </p-column> </p-dataTable>

                            </div>
                        </div>

                        <div class="col-md-12" *ngIf="!checked">
                            <div class="content table-responsive">
                                <p-dataTable #dt [value]="models" [rows]="defaultPageSize"
                                    [paginator]="true" [rowsPerPageOptions]="pageSizeOptions"
                                    [responsive]="true" resizableColumns="true" [lazy]="true"
                                    [totalRecords]="totalElements" sortField="createdDate"
                                    [sortOrder]="-1" (onLazyLoad)="loadDataIntoTable($event)"
                                    [loading]="loader" reorderableColumns="true"> <p-column
                                    field="createdDate" [sortable]="true"
                                    header="{{'movement.table.headers.date' | translate }}">
                                <ng-template let-col let-date="rowData" pTemplate="body">
                                <span> {{date[col.field] | formatdate | date
                                    :('pattern.datehourmin' | translate)}} </span> </ng-template></p-column> <p-column
                                    field="currencyDate" [sortable]="true"
                                    header="{{'movement.table.headers.currencydate' | translate }}">
                                <ng-template let-col let-date="rowData" pTemplate="body">
                                <span> {{date[col.field] | formatdate | date
                                    :('pattern.date' | translate)}} </span> </ng-template></p-column> <p-column field="description"
                                    header="{{'movement.table.headers.description' |
                            translate }}"
                                    [sortable]="true"> <ng-template
                                    let-movement="rowData" pTemplate="body"> <span
                                    *ngIf="movement.refundId || movement.ticketBundleId"
                                    class="crosslink" (click)="selectDescription(movement)">{{movement.description}}</span>
                                <span
                                    *ngIf="movement.refundId === undefined && movement.ticketBundleId === undefined">{{movement.description}}</span>
                                </ng-template> </p-column> <p-column field="paymentTypeName"
                                    header="{{'movement.table.headers.paymenttypename' |
                            translate }}"
                                    [sortable]="true"></p-column> <p-column field="amount"
                                    header="{{'movement.table.headers.amount' | translate }}"
                                    [sortable]="true"> <ng-template let-col
                                    let-amount="rowData" pTemplate="body"> <span
                                    [style.color]="amount[col.field] < 0 ? 'red' : 'green'">
                                    {{amount[col.field] | currency:'EUR':true}} </span> </ng-template></p-column> <p-column
                                    styleClass="col-button"> <ng-template
                                    let-model="rowData" pTemplate="body">

                                <div class="text-center">
                                    <span><button
                                            class="btn btn-simple btn-success btn-icon edit"
                                            (click)="selectModel(model)"
                                            matTooltip="{{'movement.table.tooltips.view' | translate }}"
                                            [matTooltipPosition]="'left'"
                                            [matTooltipShowDelay]="tooltipShowDelay"
                                            [matTooltipHideDelay]="tooltipHideDelay">
                                            <i class="material-icons">dvr</i>
                                        </button> </span>
                                </div>
                                </ng-template> </p-column> </p-dataTable>

                            </div>
                        </div>


                    </div>
                </div>
                <!--  end card  -->
            </div>
            <!-- end col-md-12 -->
            <!-- end row -->
        </div>
    </div>
</div>
2
I can't see anything wrong in this code. Is there more information about what code caused the error? - Günter Zöchbauer
Can you update your question with the component class? - RagnarLodbrok
@llqadude updated - Alessandro Celeghin
stackoverflow.com/help/mcve Please remove the code that is not required to reproduce the issue. - Günter Zöchbauer
the problem is not in your template, is in your component I solved this issue in my code adding a small timeout when I m going to setup the data to my template setTimeout(()=>{ /** my code */ }, 300) - Ricardo

2 Answers

2
votes

I was getting this ExpressionChangedAfterItHasBeenCheckedError when opening PrimeNG dialogs, specifically when either a dropdown or a radio button was the first component that grabbed focus. From this thread here:

https://github.com/primefaces/primeng/issues/4139

...it looks like a PrimeNG bug, not just something people are doing wrong using PrimeNG.

I found a workaround that I'm not crazy about, but it seems to get the job done for me for now:

// Patches for PrimeNG focus bugs.
import { Dropdown, RadioButton } from 'primeng/primeng';

const originalDropdownOnInputFocus = Dropdown.prototype.onInputFocus;
Dropdown.prototype.onInputFocus = function(event: any): void {
  setTimeout(() => {
    originalDropdownOnInputFocus.call(this, event);
  });
};

const originalRadioButtonOnFocus = RadioButton.prototype.onFocus;
RadioButton.prototype.onFocus = function(event: any): void {
  setTimeout(() => {
    originalRadioButtonOnFocus.call(this, event);
  });
};

This is not meant to be an exhaustive fix, it merely fixes the specific problems I had in my own app with focus on a p-dropdown and a p-radioButton. Hopefully this pattern can be followed to fix other bugs.

Of course, I'll be eagerly awaiting a real fix in PrimeNG itself so I can delete this workaround.

2
votes

I use the following workaround to stay away from similar issues. I add a reference to the ChangeDetectorRef service in the constructor and call its detectChanges() method as the last thing in the component's constructor with a delay:

import { ChangeDetectorRef } from '@angular/core';
....
constructor(....,
    private cdr: ChangeDetectorRef) {
    ....
    setInterval(() => {
      this.cdr.detectChanges();
    }, 1000);
  }

This has worked few times. If the error does not dissappear, you may have to debug your code and find the spot at which the error occurs and call the detect changes method after that. For example if you know that the error occurs at a particular spot in yourMethod() then you may have to modify the method like the following:

yourMethod(param):returnType {
    ....
    //spot at which the error occurs
    setInterval(() => {
      this.cdr.detectChanges();
     }, 10);
    ....
}