Angular OnPush 策略不适用于 ngFor - 总是重新创建所有子项

Angular OnPush strategy not working with ngFor - always recreates all children

我正在尝试构建一个小型框架,该框架根据提供的 JSON 描述表单结构的数据呈现表单。数据中指定的每个表单元素在运行时由一个 angular 组件表示。

表单中的元素可以使用 'container' 表单元素进行嵌套。相应的组件在数组上使用 *ngFor 循环。

我选择使用 OnPush Change Detection,效果很好 - except 用于容器组件,当数组项发生变化,因此当对@inputs 进行变化检测时,没有什么可比较的。

我试着在这里做了一个简化的例子,希望你明白这一点。

https://stackblitz.com/github/rpnoll1977/TestNgArray

为了能够分辨出哪些部分是重新创建的,我添加了一些输入字段。您可以输入一些随机文本。当您点击按钮时,一个新元素将添加到列表中。列表中的所有元素都被重新创建——而不是其他元素,尤其是与 *ngfor 并行的输入。

在 *ngFor 中提供跟踪功能后,我能够稍微调试一下。在 Angular 的 DefaultIterableDiffer.check 中,它总是将 mayBeDirty=true 设置为要比较的记录等于 null。

实际上我做了另一个例子,而不是 *ngfor 使用一系列 *ngIfs(条件是数组的长度小于 *ngif 的位置)。这显示了正确的行为,仅重新呈现添加的项目。

您应该注意如何更新模型以及如何使用 trackBy 功能。

您希望 ngFor 模型中的项目保持不变。

tracker(index:number, item:any):any{            
  return item;    
}

对于深层嵌套的项目也是如此:

controls: [
  ...this.model.controls[0].controls, <=== they stay the same
  {id:"labelX", type:"l"},  
]      


this.model.controls[0].controls[0] === newModel.controls[0].controls[0]); => true

但上面的一个容器并非如此:

var newModel = {
  ...this.model, 
  controls: [ <=== here you create a completely new array with new objects
    {
    ...


this.model.controls[0] === newModel.controls[0] // false

所以解决方案是为 trackBy 使用一些标识符,在您的情况下是 id

tracker(index:number, item:any):any{            
  return item.id;    
}

Forked Stackblitz

你在trackBy函数中使用的tracker函数是错误的

  tracker(index:number, item:any):any{            
    return item;/// wrong
  }

应该是

https://stackblitz.com/edit/github-qhynwq?file=src%2Fapp%2Fapp.component.ts

  tracker(index:number, item:any):any{            
    return item.id;
  }