微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Angular - 动态组件渲染错误数据

如何解决Angular - 动态组件渲染错误数据

解决

正如 Aaron 在下面提到的,“其他选项”方法没有问题并且感觉更干净。我会留下更新和所有内容,以防将来有人遇到类似问题并且解决方法以某种方式提供有用的信息。


我在尝试使用分页和排序在 Angular 材质表中渲染动态组件时遇到了一个奇怪的问题。

这是一个带有重现问题的 Stackblitz。从我的研究来看,这似乎是某种变化检测问题,尽管我可能错了,因为我不知道为什么会发生这种情况。如果有人对如何解决此问题有任何想法,或者我做错了什么,我们将非常感谢您提供反馈和帮助。

当前行为:

我在材料设计表中使用 @ViewChildrenComponentFactoryResolver 来动态渲染具有几页数据的组件。一切看起来都很好,但是当使用 matSort 对数据进行排序时,一些具有动态组件的行显示错误的数据值,而行的其余部分(非动态)具有正确的数据值。请参阅下面的屏幕截图:

排序前:

2

排序后:

3

组件实例的控制台日志(注意传递了正确的状态字段,但渲染了错误的字段)。

3

预期行为:

当我排序时,数据和渲染的组件会准确显示。应该匹配 dataSource.data 字段。

问题的最小重现和说明:

访问 Stackblitz 并运行应用程序。单击“订单状态”的排序标题。请注意表中的第一个条目如何显示“已交付”,而实际数据源将其显示为“已取消。也不是行的其余部分是准确的 - 只是动态呈现的组件不正确。

我的尝试:

  • 尝试使用 get() 和 set() 而不是 @input() 设置状态并注册 ngOnChage,我在其中从组件本身调用 changeDetectorRef,但问题仍然存在。

  • 尝试更改 ChangeDetectionStrategy - 没有人做任何事情。

  • 也尝试了以下部分中的解决方

我在没有提到解决方案的情况下看过的可能相关帖子

这似乎是同一个问题,但从未有人回答过。我希望提供 Stackblitz 将有助于获得相同的答案。根据海报,这似乎是某种变化检测问题。

When loading same component with different data,old data still showing - Angular 5

尝试为 componentRefs、父组件、动态创建的实际组件显式调用 detectChanges() 和 markForCheck() chanceDetectionRef 方法

Angular dynamic component loader change detection issue

Change detection not working when creating a component via ComponentFactoryResolver

可能的解决方法/更奇怪的行为:

如果您按订单状态排序并像以前一样将错误的值放在第一行,然后分页下一页并返回,则组件显示正确的值。我假设分页会强制在材料表、模板或类似内容中进行某种更改检测,但我不知道为什么排序不会这样做。我目前要做的工作是分页下一页并返回,但这绝不是一个可以接受的解决方案,我真的很想了解我在这里做错了什么,或者对此有什么解决方法是。

更新 1

根据下面 Aaron 的其他见解,我添加一个 Updated Stackblitz一个更简单的示例。仔细观察,分页似乎对这种奇怪的行为没有影响。我可以只用 2 个表条目和一个排序(删除所有分页代码)来重现这个问题。我已更改传入的数据以匹配各行,因此更清楚哪些行来自何处​​。

以下是使用 2、3 和 4 条记录排序前后的组合屏幕截图:

enter image description here

我目前正在尝试查看是否可以强制重新渲染表格(假设它可能会改变内容),并最终深入研究表格的源代码

更新 2

我能想出的一种解决方法是在我在执行过滤逻辑之前使用 splice() 中的 onSortChange() 保存它的副本后,将我的 dataSource 设置为一个空数组,然后将其设置回过滤后的数组。我假设这会触发材料数据源/表中的某种更新,从而强制重新渲染或更新。我会继续尝试找出是否有更好的方法解决这个问题,因为这样做似乎很奇怪,并且在我的原始应用程序中,我有一个 filterPredicate,它在过滤/重置过滤器时也会导致同样的问题。

更新 3

Aaaaa,最后经过对此的更多研究,我至少找到了一个有意义的解决方案。在更新 #2 上说完之后,我找到了这篇文章

https://github.com/angular/components/issues/15972#issuecomment-490235603

这解释了为什么表没有获得更新的数据源。在我的情况下,由于我正在执行排序/过滤器,因此可能很难弄清楚这一点,并且不认为它会直接与此相关联。在接下来的几天里,我仍然会探索更多的想法,但如果两者都不起作用,我会继续将其标记为已解决

解决方法

我不是 100% 正在发生的事情,但稍微玩弄一下,我认为问题不是变化检测。我首先在

之后注释掉所有内容

cellTemplateRef.clear();createDynamicComponents() 中的 dynamic-table.components.ts

这样做,肯定会看到该列中的所有内容都消失了。

然后采取另一种方法,只注释掉 cellTemplateRef.clear(),您会看到新组件被推到旧组件之上。

enter image description here

下一步是在添加新组件后删除旧组件...

在 Object.keys.... 循环之后:

    if (cellTemplateRef.length > 1) {
      cellTemplateRef.remove(1);
    } 

不知道为什么 clear() 不能完成这项工作,但删除清除并添加组件实例的直接删除似乎是一种解决方法。

注意到这个解决方案甚至没有正确排序,这里有一些关于正在发生的事情的更多见解。在您的 StatusIconComponent 上添加 @Input() 索引并将其显示在您的组件中。然后在你的循环中......

cellComponentRef.instance["index"] = i;

排序后,您将在排序前看到如下内容:

enter image description here

然后排序后...

enter image description here

请注意,行未按您期望的顺序呈现。

有了这个,我怀疑您甚至不必一直添加/删除组件。他们可能不会重新渲染整行,也许只是移动它。

这仍然不能回答您的问题,但希望能提供更多见解。

其他选项

我怀疑数据单元格中 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>

这样,您就不必关心数据表的内部工作以及呈现的顺序等。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。