angular - Angular - 动态组件渲染错误数据
问题描述
解决了
正如 Aaron 在下面提到的,“其他选项”方法没有问题并且感觉更干净。我将保留更新和所有内容,以防将来有人遇到类似问题,并且解决方法以某种方式提供有用。
我在尝试使用分页和排序在 Angular 材质表中渲染动态组件时遇到了一个奇怪的问题。
这是一个带有复制问题的Stackblitz 。根据我的研究,这似乎是某种变化检测问题,尽管我可能错了,因为我不知道为什么会发生这种情况。如果有人对如何解决此问题有任何想法,或者如果我做错了什么,我们将非常感谢您提供反馈和帮助。
当前行为:
我在材料设计表中使用@ViewChildren和ComponentFactoryResolver来使用几页数据动态渲染组件。一切看起来都很好,但是当使用 matSort 对数据进行排序时,一些具有动态组件的行显示错误的数据值,而其余的行(非动态)具有正确的数据值。请参阅下面的屏幕截图:
排序前:
排序后:
组件实例的控制台日志(注意传递了正确的状态字段,但呈现了错误的状态字段)。
预期行为:
当我排序时,数据和渲染的组件会准确显示。应该匹配 dataSource.data 字段。
使用说明最小限度地再现问题:
访问Stackblitz并运行应用程序。单击“订单状态”的排序标题。请注意表中的第一个条目如何显示“已交付”,而实际数据源将其显示为“已取消”。也不是该行的其余部分是准确的 - 只是动态呈现的组件不正确。
我试过的:
尝试使用 get() 和 set() 而不是 @Input() 设置状态并注册 ngOnChage,我在其中从组件本身调用 changeDetectorRef,但问题仍然存在。
尝试更改 ChangeDetectionStrategy - 没有人做任何事情。
也尝试了以下部分中的解决方案
在没有提到解决方案的情况下,我看过的可能的相关帖子
这似乎是同一个问题,但从未得到回答。我希望提供 Stackblitz 将有助于获得相同的答案。根据海报,这似乎是某种变化检测问题。
加载具有不同数据的相同组件时,仍然显示旧数据 - Angular 5
尝试显式调用 componentRefs 的 detectChanges() 和 markForCheck() chanceDetectionRef 方法,父组件是动态创建但没有运气的实际组件。
通过 ComponentFactoryResolver 创建组件时更改检测不起作用
可能的解决方法/更奇怪的行为:
如果您按订单状态排序并像以前一样在第一行中获取错误的值,然后分页到下一页并返回,组件显示正确的值。我假设分页强制在材料表或模板中进行某种更改检测,或类似的东西,但我不知道为什么排序不会这样做。目前我要做的工作是分页到下一页并返回,但这绝不是一个可接受的解决方案,我真的很想了解我在这里做错了什么,或者对此有什么解决方法是。
更新 1
根据下面 Aaron 的其他见解,我添加了一个更新的 Stackblitz,其中包含一个更加简化的示例。仔细观察,分页似乎并没有成为奇怪行为的一个因素。我只需 2 个表格条目和一个排序(所有分页代码都已删除)即可重现该问题。我已经更改了传入的数据以匹配各行,因此更清楚哪些行来自哪里。
这是使用 2、3 和 4 记录排序前后的组合屏幕截图:
我目前正在尝试查看是否可以强制表格重新渲染(假设它可能会改变事情),并最终会挖掘表格的源代码。
更新 2
我能够想出的一种解决方法是在执行过滤逻辑之前将我的 dataSource 的副本保存为右侧的副本后,将其设置为空数组splice()
,onSortChange()
然后将其设置回过滤后的数组。我假设这会触发材质数据源/表中的某种更新,从而强制重新渲染或更新。我会继续尝试找出是否有更好的方法来解决这个问题,因为这样做似乎很奇怪,而且在我的原始应用程序中,我有一个 filterPredicate ,它在过滤/重置过滤器时也会导致同样的问题。
更新 3
Aaaaaand 最后在对此进行了更多研究之后,我至少找到了一个有意义的解决方案。在我在更新 #2 上说完之后,我发现了这篇文章:
https://github.com/angular/components/issues/15972#issuecomment-490235603
这就解释了为什么表没有得到更新的数据源。就我而言,由于我正在进行排序/过滤,因此可能很难弄清楚这一点,并且认为它不会直接与此相关。在接下来的几天里,我仍然会探索更多的想法,但如果两者都不起作用,我会在此之后将其标记为已解决。
解决方案
我不是 100% 确定发生了什么,但稍微玩弄一下,我认为问题不在于更改检测。我首先注释掉之后的所有内容
cellTemplateRef.clear();
在文件中createDynamicComponents()
。dynamic-table.components.ts
这样做,肯定会看到该列中的所有内容都消失了。
然后采用仅注释掉的另一种方法
cellTemplateRef.clear()
,您会看到您的新组件被推到旧组件之上。
下一步是在添加新组件后删除旧组件...
在 Object.keys.... 循环之后:
if (cellTemplateRef.length > 1) {
cellTemplateRef.remove(1);
}
不知道为什么clear()
不做这项工作,但删除清除并添加组件实例的直接删除似乎是一种解决方法。
注意到这个解决方案甚至没有正确排序,这里有一些关于正在发生的事情的更多见解。在您的组件上添加一个 @Input() 索引StatusIconComponent
并将其显示在您的组件中。然后在你的循环中......
cellComponentRef.instance["index"] = i;
排序后,您将在排序之前看到如下内容:
然后排序后...
请注意,这些行未按您期望的顺序呈现。
有了这个,我怀疑您甚至不必一直添加/删除组件。他们可能不会重新渲染整行,也许只是移动它。
这仍然不能回答您的问题,但希望能提供更多见解。
其他选项
我怀疑数据单元格中 ng-template 上 ViewChildren 的顺序不是您希望的。因此,与其在表格中创建模板,不如创建一个DynamicCellComponent
您可以在其中执行所有组件工厂魔术的模板。
所以你的表格模板看起来像这样......
<td mat-cell *matCellDef="let data">
<!-- moving your ngIfs to the component -->
<app-dynamic-cell [columnDef]="column" [data]="data"></app-dynamic-cell>
</td>
然后创建一个与此类似的组件(基本上将大部分代码从表中移出)。
@Component({
selector: "app-dynamic-cell",
templateUrl: "./dynamic-cell.component.html",
styleUrls: ["./dynamic-cell.component.css"]
})
export class DynamicCellComponent implements AfterViewInit {
@Input() columnDef: DynamicTableColumn;
@Input() data: any;
@ViewChild("container", { read: ViewContainerRef })
private container: ViewContainerRef;
constructor(private componentFactoryResolver: ComponentFactoryResolver, private cdr: ChangeDetectorRef) {}
isValueDynamicComponent(value: any) {
return !(typeof value === "function");
}
ngAfterViewInit() {
console.log(this.columnDef, this.data);
const viewContainerRef = this.container;
if (viewContainerRef) {
viewContainerRef.clear();
const { type: componentType, inputs: inputsFn } = (this.columnDef
.value as any) as DynamicComponent;
const componentInputs = inputsFn(this.data);
// create the component instance and store a reference to it
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
componentType
);
const cellComponentRef = viewContainerRef.createComponent<
typeof componentType
>(componentFactory);
// populate any properties of the component
Object.keys(componentInputs).forEach(key => {
cellComponentRef.instance[key] = componentInputs[key];
});
this.cdr.detectChanges();
}
}
}
然后组件 html 执行您之前在单元格定义中所做的操作。
<ng-container *ngIf="!isValueDynamicComponent(columnDef.value)">{{ columnDef.value(data)}}</ng-container>
<ng-container *ngIf="isValueDynamicComponent(columnDef.value)"#container></ng-container>
这样,您就不必关心数据表的内部工作,以及渲染的顺序等。
推荐阅读
- generics - Ada 含糊不清的表达与 Get
- java - 如何在日历中每个不同的点击日期保存不同的文本?
- scala - 将列表转换为 Map 中的 TreeMap
- selenium - WebElement click() 在 Selenium 中不起作用
- java - 比较 id 的两个属性列表并仅从 list1 中删除该对象,而不是从 list2 中删除,然后合并到 java 中的单个列表中
- html - 如何在引导程序中为多行和多列制作水平滚动条?
- python - 创建一个新列,它是 python 中另一列的 value_counts
- python-3.x - sqlite3.ProgrammingError:在线程中创建的 SQLite 对象只能在同一线程中使用。在 django 2.2 中
- python - django模板获取键和值分开显示
- jenkins - 我可以根据 Jenkins 构建更改 JIRA 问题状态吗