2
votes

ExpressionChangedAfterItHasBeenCheckedError - incrémentation d'un nombre et affichage du nombre dans la vue

voir Stackblitz ici .

Je veux afficher un affichage en direct de callCount lorsque rowCallback() est appelé lorsque chacune des cellules de Kendo Grid est vérifiée.

Je m'attends à ce que le numéro de callCount final soit 12 (c'est-à-dire 3 lignes x 4 cellules chacune = 12).

J'obtiens l'erreur ExpressionChangedAfterItHasBeenCheckedError classique. Rien d'utile n'a été mentionné sur le site Telerik: https://www.telerik.com/kendo-angular-ui/components/grid/api/GridComponent/#toc-rowclass

Code pour plus de commodité:

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
 }
];


2 commentaires

Pouvez-vous également ajouter une capture d'écran de l'erreur de console?


J'ai ajouté une réponse à votre requête et je ne pense pas que vous devriez utiliser la liaison de propriété (liaison rowClass) pour incrémenter une autre propriété de composant. C'est une règle de base angulaire que l'état du composant ne doit pas changer pendant la liaison de propriété.


4 Réponses :


0
votes

Je ne pense pas que vous devriez utiliser la méthode rowClass pour calculer le nombre de cellules dans la grille kendo.

Comme spécifié dans la documentation de rowClass, vous devez utiliser pour définir dynamiquement le nom de la classe Css. Je pense que vous pouvez facilement atteindre votre objectif en utilisant les méthodes ci-dessous de base sur les scénarios suivants:

  • Cas 1: Lorsque vous connaissez le nombre de colonnes que vous souhaitez afficher sur la grille Kendo, vous pouvez créer une propriété qui contiendra ses valeurs et l'utiliser dans l'expression du modèle comme suit:

     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
     }
     ];
    
  • Cas-2: Lorsque vous n'êtes pas conscient du nombre de colonnes que vous souhaitez afficher sur la grille Kendo, vous pouvez le calculer dynamiquement sur la charge de votre composant:

    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
    }
    ];
    

Dans tous les cas, vous pouvez trouver le nombre de colonnes avant de rendre la grille et vous pouvez l'utiliser pour calculer le nombre de grille.


0 commentaires

-3
votes

Vous pouvez utiliser le rappel setTimeout dans vos méthodes rowCallback . Pour éviter ExpressionChangedAfterItHasBeenCheckedError erreur ExpressionChangedAfterItHasBeenCheckedError .

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


0 commentaires

2
votes

Le problème

Comme l'indique l'erreur, vous définissez la valeur d'une propriété dans la vue après la fin du cycle de vie angulaire, ( AfterContentHasBeenChecked )

Considérez ce scénario

Vous définissez la valeur de callCount = 0 , dans votre html vous avez <div> 0 </div>
Le contenu est vérifié.
La 1ère ligne est chargée
Maintenant, nous mettons à jour callCount = 1 et html <div> 1 </div>
Cela déclenche la détection des changements et donc l'erreur

Si nous voulons implémenter l'approche ci-dessus, nous devrons informer anguar de ce changement pour lequel nous pouvons utiliser ChangeDetectorRef . Essayons maintenant d'implémenter ce qui précède avec ceci

Vous définissez la valeur de callCount = 0 , dans votre html vous avez <div> 0 </div>
Détection de changement de déclenchement Le contenu est vérifié.
La 1ère ligne est chargée
Maintenant, nous mettons à jour callCount = 1 et html <div> 1 </div>
Détection de changement de déclenchement La 2ème rangée est chargée
Maintenant, nous mettons à jour callCount = 2 et html <div> 2 </div>
Détection de changement de déclenchement La 3ème rangée est chargée
Maintenant, nous mettons à jour callCount = 3 et html <div> 3 </div>
Détection de changement de déclenchement La 1ère ligne est chargée
Maintenant, nous mettons à jour callCount = 4 et html <div> 4 </div>
Détection de changement de déclenchement La 2ème rangée est chargée
Maintenant, nous mettons à jour callCount = 5 et html <div> 5 </div>
Détection de changement de déclenchement La 3ème rangée est chargée
Maintenant, nous mettons à jour callCount = 6 et html <div> 6 </div>
Détection de changement de déclenchement
...
Cette boucle ne se terminera jamais et angulaire lancera une erreur Maximum stack reached

Ce qui précède est ce que Angular essaie de vous avertir. Cela indique qu'il y a un problème avec votre approche

Solution

Vous aurez besoin d'un moyen de déterminer le nombre de lignes avant que le contenu ne soit vérifié

L'option la plus simple sera de compter le nombre de produits et de le multiplier par le maximum du nombre de propriétés dans chaque article

    <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 -->

Dans le code ci-dessus, j'utilise BehaviorSubject pour stocker les données en tant Observable . De cette façon, nous pouvons utiliser le piping et l'opérateur map pour obtenir la propriété length des produits (qui sera essentiellement le nombre de lignes de la table)

Dans le html, nous pouvons utiliser un tube asynchrone pour nous abonner à ces Observables

 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)
  }

Essayez de cliquer sur le bouton, cela mettra à jour le tableau et le nombre de lignes

Voir cette solution sur stackblitz


0 commentaires

0
votes

Le problème est que vous mettez à jour la vue après AfterViewChecked

La seule solution à votre implémentation que je vois est d'utiliser 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) {}
}


2 commentaires

Erreur: Maximum call stack size exceeded


C'est parce que rowCallback est appelé à chaque détection de changement. Trouvons une solution appropriée à votre problème. Cela ne devrait pas être si compliqué. Quel est votre objectif avec la tâche?