简体   繁体   English

作为元素属性的函数和 Angular ExpressionChangedAfterItHasBeenCheckedError

[英]Functions as Element Attributes and the Angular ExpressionChangedAfterItHasBeenCheckedError

I'm creating a summary row on a tree table that also has filtering (I'm using the ng Prime component library, but I don't think that's germane to the error or question).我正在树表上创建一个摘要行,该表也具有过滤功能(我使用的是 ng Prime 组件库,但我认为这与错误或问题无关)。 I want my summary row to only summarize the information currently in view, thus the summary should change when the tree is expanded or when a filter is applied.我希望我的汇总行只汇总当前查看的信息,因此当展开树或应用过滤器时,汇总应该改变。

I achieved this goal by adding row data to a map via a function I set as an element attribute [attr.summaryFunction]="summaryFunction(i,rowData[col.field])", and reinitializing the map with ngDoCheck.我通过我设置为元素属性 [attr.summaryFunction]="summaryFunction(i,rowData[col.field])" 的 function 添加行数据到 map 并使用 ngDoCheck 重新初始化 map 来实现这个目标。 Without this step, reinitializing the map in ngDoCheck, totals accumulate with every repaint or change and result in incorrect values.如果没有这一步,在 ngDoCheck 中重新初始化 map,每次重绘或更改都会累积总计,并导致不正确的值。 (I tried reinitializing the map with several other lifecycle hooks ngOnInit, ngOnViewInit, and ngOnChanges, but the only lifecycle hook that effectively reinitialized the map was ngDoCheck). (我尝试使用其他几个生命周期挂钩 ngOnInit、ngOnViewInit 和 ngOnChanges 重新初始化 map,但唯一有效重新初始化 map 的生命周期挂钩是 ngDoCheck)。

My approach works perfectly (yay me,), but I get an ExpressionChangedAfterItHasBeenCheckedError in the browser, three times.我的方法非常有效(是的,我,),但我在浏览器中得到了 ExpressionChangedAfterItHasBeenCheckedError 三次。 for each column, To address this error: I tried all 4 approaches to fixing the error recommended in the documentation:对于每一列,为了解决这个错误:我尝试了所有 4 种方法来修复文档中推荐的错误:

  1. using different lifecycle hooks --> totals are incorrect使用不同的生命周期挂钩 --> 总数不正确
  2. I added this.cd.detectChanges() to summary function --> infinite loop我将 this.cd.detectChanges() 添加到摘要 function --> 无限循环
  3. I wrapped the if statement in setTimeout(() => if(statement){}) --> infinite loop我将 if 语句包装在 setTimeout(() => if(statement){}) --> 无限循环中
  4. Promise.resolve().then(() => if(statement){}) --> infinite loop Promise.resolve().then(() => if(statement){}) --> 无限循环

Again, my approach currently works exactly as I want it to, I just want these annoying errors to go away.同样,我的方法目前完全按照我想要的方式工作,我只是希望这些烦人的错误 go 消失。 I'd also like to understand why the usual fixes cause an infinite loop.我还想了解为什么通常的修复会导致无限循环。

Here's my TS layer;这是我的 TS 层;

export class PrimetreeComponent implements OnInit, DoCheck {
sampleData: TreeNode[] = []
columns: any[] = [];
summaryMap = new Map();
columnIndex = 0;

constructor(private nodeService: NodeService, private cd: ChangeDetectorRef) {}


  ngOnInit(): void {
    this.nodeService.getFilesystem().subscribe(data => {this.sampleData = data});

      this.columns = [
      {field: 'name', header: 'name'},
      {field: 'size', header: 'size'},
      {field: 'type', header: 'type'}]
  }

 ngDoCheck(): void {
  this.summaryMap = new Map();
 }

  summaryFunction(columnIndex:number, rowData:any){
    
    if(this.summaryMap.has(columnIndex)){
      //if row data is a non-number string we just count the rows
      if(Number(rowData)){
        this.summaryMap.set(columnIndex, this.summaryMap.get(columnIndex)+Number(rowData));
      } else {
        this.summaryMap.set(columnIndex, this.summaryMap.get(columnIndex)+1)
      }
    } else {
      if(Number(rowData)){
        this.summaryMap.set(columnIndex, Number(rowData));
      } else {
        this.summaryMap.set(columnIndex, 1)
      }
    } 
  }

}

And here's my HTML Template:这是我的 HTML 模板:

<p-treeTable #tt [value]="sampleData" [columns]="columns">
  <ng-template pTemplate="caption">
    <div style="text-align: right">
      <span class="p-input-icon-left">
        <i class="pi pi-search"></i>
        <input
          pInputText
          type="text"
          size="50"
          placeholder="Global Filter"
          (input)="tt.filterGlobal($any($event.target).value, 'contains')"
          style="width:auto"
        />
      </span>
    </div>
  </ng-template>
  <ng-template pTemplate="header" let-columns>
    <tr>
      <th *ngFor="let col of columns">
        {{ col.header }}
      </th>
    </tr>
    <tr>
      <th *ngFor="let col of columns">
        <input
          pInputText
          type="text"
          class="w-full"
          (input)="
            tt.filter($any($event.target).value, col.field, col.filterMatchMode)
          "
        />
      </th>
    </tr>
  </ng-template>
  <ng-template pTemplate="body" let-rowNode let-rowData="rowData" >
    <tr>
      <td *ngFor="let col of columns; let i = index;" [attr.summaryFunction]="summaryFunction(i,rowData[col.field])">
        <p-treeTableToggler
          [rowNode]="rowNode"
          *ngIf="i == 0"
          (columnIndex)="i"
        ></p-treeTableToggler>
        {{ rowData[col.field] }}
      </td>
    </tr>
  </ng-template>
  <ng-template pTemplate="emptymessage">
    <tr>
      <td [attr.colspan]="columns.length">No data found.</td>
    </tr>
  </ng-template>
<ng-template pTemplate="summary">
  <tr>
    <td *ngFor="let col of columns;  let i = index;">
      {{ col.header }}--sumMap--{{summaryMap.get(i) }}
    </td>
  </tr>
</ng-template>

I am not familiar with the approach of binding a function to an elements attribute, but it seems kind of hacky to me.我不熟悉将 function 绑定到元素属性的方法,但对我来说似乎有点老套。 Correct me if I'm wrong, but the reason for this is to have a way to trigger the function when the node is added to the dom?如果我错了请纠正我,但这样做的原因是有一种方法可以在将节点添加到 dom 时触发 function?

I did not test this myself, but I think you could make use of the @ContentChildren decorator see docs .我自己没有对此进行测试,但我认为您可以使用@ContentChildren装饰器,请参阅文档 It exposes an observable that you can subscribe to to react to changes of the content dom.它公开了一个可观察对象,您可以订阅该对象以对内容 dom 的更改做出反应。 Ideally, you could run the summary creation and updating the summaryMap in one step.理想情况下,您可以一步运行摘要创建和更新 summaryMap。

Update:更新:

I played around with the idea a bit.我试了一下这个想法。 One solution for what you are trying to achieve is to use @ViewChildren decorator to get a QueryList of all the rows currently rendered.您要实现的目标的一种解决方案是使用@ViewChildren装饰器获取当前呈现的所有行的 QueryList 。 This can be achieved in the following way:这可以通过以下方式实现:

 <ng-template pTemplate="body" let-rowNode let-rowData="rowData" >
    <tr [ttRow]="rowNode" #tableRow [attr.file-size]="rowData.size">
      <td>
        <p-treeTableToggler [rowNode]="rowNode"></p-treeTableToggler>
        {{ rowData.name }}
      </td>
      <td>{{ rowData.size }}</td>
      <td>{{ rowData.type }}</td>
    </tr>
  </ng-template>

Note the template ref on <tr [ttRow]="rowNode" #tableRow [attr.file-size]="rowData.size"> along with binding the rowData.size to an attribute of the element.请注意<tr [ttRow]="rowNode" #tableRow [attr.file-size]="rowData.size">上的模板引用以及将 rowData.size 绑定到元素的属性。

You can then grab that list of rows rows that are currently mounted to the dom like so:然后,您可以获取当前安装到 dom 的行列表,如下所示:

// inside your component

@ViewChildren('tableRow') view_children!: QueryList<ElementRef>;

// get the list, calculate initial sum and subscribe to changes. 
// We do that in ngAfterViewInit lifecycle hook as the 
// list is guaranteed to be available at that point of time:

ngAfterViewInit() {
  this.calculateSum(this.view_children);

  this.view_children.changes.subscribe(
    (list: QueryList<ElementRef>) =>
      this.calculateSum(list)
  );
}


You can find a running example of this here .您可以在此处找到一个运行示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM