ExpressionChangedAfterItHasBeenCheckedError - 递增一个数字并在视图中显示该数字

ExpressionChangedAfterItHasBeenCheckedError - incrementing a number and showing the number in view

查看 Stackblitz here

我想显示 callCount 的实时显示,因为 rowCallback() 在检查每个 Kendo 网格单元格时被调用。

我希望最终的 callCount 数字为 12(即 3 行 x 4 个单元格,每行 = 12)。

我遇到了典型的 ExpressionChangedAfterItHasBeenCheckedError 错误。 Telerik 网站上没有提到任何有用的内容:https://www.telerik.com/kendo-angular-ui/components/grid/api/GridComponent/#toc-rowclass

方便代码:

import { Component, ViewEncapsulation } from '@angular/core';
import { RowClassArgs } from '@progress/kendo-angular-grid';

@Component({
   selector: 'my-app',
   encapsulation: ViewEncapsulation.None,
   styles: [`
       .k-grid tr.even { background-color: #f45c42; }
       .k-grid tr.odd { background-color: #41f4df; }
   `],
   template: `
       <kendo-grid [data]="gridData" [rowClass]="rowCallback">
       </kendo-grid>
       <div>{{callCount}}</div> <!-- Double bind to show the current number -->
   `
})
export class AppComponent {
   public gridData: any[] = products;
   public callCount: number = 0;

   public rowCallback = (context: any) => {
       this.callCount++; // Incrementing the number
   }
}

const products = [{
   "ProductID": 1,
   "ProductName": "Chai",
   "UnitPrice": 18.0000,
   "Discontinued": true
 }, {
   "ProductID": 2,
   "ProductName": "Chang",
   "UnitPrice": 19.0000,
   "Discontinued": false
 }, {
   "ProductID": 3,
   "ProductName": "Chang",
   "UnitPrice": 28.0000,
   "Discontinued": false
 }
];

我认为您不应该使用 rowClass 方法来计算 kendo 网格中的单元格数。

如行Class documentation 中指定,您应该使用动态设置Css Class 名称。我认为您可以根据以下场景使用以下方法轻松实现您的目的:

  • 案例 1: 当您知道要在 Kendo 网格上显示的列数时,您可以创建一个 属性 将保存它的值并在模板表达式中使用它,如下所示:

    import { Component, ViewEncapsulation } from '@angular/core';
    import { RowClassArgs } from '@progress/kendo-angular-grid';
    
    
    
    @Component({
       selector: 'my-app',
       encapsulation: ViewEncapsulation.None,
       styles: [`
           .k-grid tr.even { background-color: #f45c42; }
           .k-grid tr.odd { background-color: #41f4df; }
        `],
       template: `
           <kendo-grid [data]="gridData">
           </kendo-grid>
           <div>{{ columnCount * gridData.length }}</div> <!-- Double bind to show the current number-->
         `
    })
     export class AppComponent {
     public gridData: any[] = products;
     public callCount: number = 0;
     public readonly columnCount = 4; // let say grid has 4 column (known prior)
    }
    
     const products = [{
    "ProductID": 1,
    "ProductName": "Chai",
    "UnitPrice": 18.0000,
    "Discontinued": true
    }, {
     "ProductID": 2,
     "ProductName": "Chang",
     "UnitPrice": 19.0000,
      "Discontinued": false
    }, {
     "ProductID": 3,
     "ProductName": "Chang",
     "UnitPrice": 28.0000,
     "Discontinued": false
    }
    ];
    
  • Case-2: 当您不知道要在 Kendo 网格上显示的列数时,您可以在您的组件负载上动态计算它:

     import { Component, ViewEncapsulation } from '@angular/core';
     import { RowClassArgs } from '@progress/kendo-angular-grid';
    
    
    
     @Component({
        selector: 'my-app',
        encapsulation: ViewEncapsulation.None,
        styles: [`
            .k-grid tr.even { background-color: #f45c42; }
            .k-grid tr.odd { background-color: #41f4df; }
         `],
        template: `
            <kendo-grid [data]="gridData">
            </kendo-grid>
            <div>{{ columnCount * gridData.length }}</div> <!-- Double bind to show the current number-->
          `
     })
      export class AppComponent {
      public gridData: any[] = products;
      public callCount: number = 0;
      public readonly columnCount = Object.keys(gridData); 
     }
    
      const products = [{
     "ProductID": 1,
     "ProductName": "Chai",
     "UnitPrice": 18.0000,
     "Discontinued": true
     }, {
      "ProductID": 2,
      "ProductName": "Chang",
      "UnitPrice": 19.0000,
       "Discontinued": false
     }, {
      "ProductID": 3,
      "ProductName": "Chang",
      "UnitPrice": 28.0000,
      "Discontinued": false
     }
     ];
    

在任何情况下,您都可以在呈现网格之前找到列数,并可以使用它来计算网格数。

您可以在 rowCallback 方法中使用 setTimeout 回调。避免 ExpressionChangedAfterItHasBeenCheckedError 错误。

public rowCallback = (context: any) => {
  setTimeout(() => {
    this.callCount++; // Incrementing the number
  });
}

问题

如错误所述,您正在 Angular 生命周期结束后在视图中设置 属性 的值,(AfterContentHasBeenChecked)

考虑这种情况

You set the value of callCount = 0, in your html you have <div> 0 </div>
The content is checked.
The 1st row is loaded
Now we update callCount = 1 and html <div> 1 </div>
This triggers change detection and hence the error

如果我们要实施上述方法,我们将必须通知 anguar 我们可以使用 ChangeDetectorRef 进行的此更改。现在让我们尝试用这个

来实现上面的内容

You set the value of callCount = 0, in your html you have <div> 0 </div>
Trigger change detection The content is checked.
The 1st row is loaded
Now we update callCount = 1 and html <div> 1 </div>
Trigger change detection The 2nd row is loaded
Now we update callCount = 2 and html <div> 2 </div>
Trigger change detection The 3rd row is loaded
Now we update callCount = 3 and html <div> 3 </div>
Trigger change detection The 1st row is loaded
Now we update callCount = 4 and html <div> 4 </div>
Trigger change detection The 2nd row is loaded
Now we update callCount = 5 and html <div> 5 </div>
Trigger change detection The 3rd row is loaded
Now we update callCount = 6 and html <div> 6 </div>
Trigger change detection
...
This loop will never end and angular will throw error Maximum stack reached

以上是 angular 试图警告您的内容。说明你的做法有问题

解决方案

您需要一种方法来确定行数在检查内容之前

最简单的选择是计算产品数量并将其乘以每个项目中属性的最大值


 import { BehaviorSubject, combineLatest,  Observable, of } from "rxjs";
 import { map } from "rxjs/operators";

  // Create a new Observable using Behaviour subject to store current products
  private gridDataSubject$ = new BehaviorSubject(products);
  public gridData: Observable<any[]> = this.gridDataSubject$.asObservable();
  
  // Extract the row count being length of the products array
  private rowCount: Observable<number> = this.gridData.pipe(
    map(({ length }) => length)
  );

  // Extract column count being the Maximum of the product with the highest number of properties
  private columCount: Observable<number> = this.gridData.pipe(
    map((data) => data.map(item => Object.keys(item).length)),
    map(data => Math.max(...data))
  );

  // Define to total calls being rows X columns
  public callCount: Observable<number> = combineLatest([this.columCount, this.rowCount]).pipe(map(([row, column]) => row * column))

  public rowCallback = (context: any) => {};
  updateValue() {
    this.gridDataSubject$.next(products2);
  }

  public rowCallback = (context: any) => {};
  updateValue() {
    this.gridDataSubject$.next(products2)
  }

在上面的代码中,我使用 BehaviorSubject 将数据存储为 Observable。这样我们就可以使用 pipingmap 运算符来获取产品的 length 属性(这实际上是 [=72= 的行数) ])

在html中,我们可以使用异步管道来订阅这个Observables

    <button (click)='updateValue()'>Update</button>
    <kendo-grid [data]="gridData | async" [rowClass]="rowCallback">
    </kendo-grid>
    <div>{{ callCount | async }}</div>
    <!-- Double bind to show the current number -->

尝试点击按钮,这将更新 table 和行数

See this Solution on stackblitz

问题是您在 AfterViewChecked

之后更新视图

我看到的唯一解决方案是使用 ChangeDetectorRef:

export class AppComponent {
   public gridData: any[] = products;
   public callCount: number = 0;

   public rowCallback = (context: any) => {
       this.callCount++; // Incrementing the number
       this.changeDetectorRef.detectChanges();
   }

   constructor(private changeDetectorRef: ChangeDetectorRef) {}
}