简体   繁体   English

Angular 嵌套 ngFor 不会触发更改检测

[英]Angular Change Detection is not triggered with nested ngFor

I am currently working on a larger Angular project with a NgRx Store.我目前正在开发一个带有 NgRx 存储的更大的 Angular 项目。 The app contains a infinite scrolling list that when reaching the end shows a few skeleton items that when the request is finished get pushed down by real items.该应用程序包含一个无限滚动列表,当到达末尾时会显示一些骨架项目,当请求完成时,这些骨架项目会被真实项目下推。

In one component we need a grouped/nested list which results in a nested *ngFor .在一个组件中,我们需要一个分组/嵌套列表,这会导致嵌套*ngFor Since this is recomended for working with NgRx the component uses ChangeDetectionStrategy.OnPush .由于建议使用 NgRx,因此该组件使用ChangeDetectionStrategy.OnPush

Now when loading the list values from the backend the list takes a very long time to update (and at times only updates when clicking inside the browser) even though the request has been already finished for a long time.现在,当从后端加载列表值时,即使请求已经完成很长时间,列表也需要很长时间才能更新(有时只有在浏览器内单击时才会更新)。 My first guess would be that there is a problem with the change detection.我的第一个猜测是变更检测存在问题。

This is an excerpt of what my component looks like at the moment:这是我的组件目前的样子的摘录:

<ul cdkScrollable #groupList>
  <li *ngFor="let dayGroup of groupsWithSkeleton$ | ngrxPush | keyvalue: asIsOrder;
              let i = index;
              trackBy: trackByKey">
    <div>{{ dayGroup.key | date }}</div>
    <ul>
      <li *ngFor="let subGroup of dayGroup.value | keyvalue: asIsOrder;
                  let i = index;
                  trackBy: trackByKey">
        <!-- stuff -->
      </li>
    </ul>
  </li>
</ul>
private groups$: Observable<Groups> = this.store.select(
  Selectors.selectGroups
);

constructor(readonly store: Store<AppState>) {
  this.groupsWithSkeleton$ = combineLatest([
    this.groups$,
    this.isLoadingOrHasMoreItemsAvailable$
  ]).pipe(
    map(([groups, isLoadingOrHasMoreItemsAvailable]) => {
      const skeleton: Groups = isLoadingOrHasMoreItemsAvailable ? this.skeletonMap : new Map();
      return new Map([...groups, ...skeleton]);
    })
  );
  // [...] more assignments
}

I already kind of found a working solution, but I think it is rather ugly and I don't realy understand why this does not work in the first place.我已经找到了一个可行的解决方案,但我认为它相当难看,我真的不明白为什么这首先不起作用。 (It was still not perfectly fast but acceptable.) (它仍然不是非常快但可以接受。)

The solution was to add this after the creation of the group with skeleton items:解决方案是在创建带有骨架项目的组后添加它:

tap(() => {
  changeDetectorRef.markForCheck();
  changeDetectorRef.detectChanges();
})

I also created a StackBlitz with a MWE: https://stackblitz.com/edit/angular-ivy-7ycaxv?file=src/app/app.component.ts我还用 MWE 创建了一个 StackBlitz: https://stackblitz.com/edit/angular-ivy-7ycaxv?file=src/app/app.component.ts

Maybe someone can help me to find the underlying problem and a better solution.也许有人可以帮助我找到潜在的问题和更好的解决方案。

That's a tricky one where the documentation is really not explicit about it:这是一个棘手的问题,文档实际上并没有明确说明:

We can find in the code source of the scroll dispatcher about scrolled :我们可以在滚动调度器的代码源中找到关于scrolled的:

In order to avoid hitting change detection for every scroll event, all of the events emitted from this stream will be run outside the Angular zone.为了避免对每个滚动事件进行更改检测,从此 ZF7B44CFFAFD5C52223D5498196C8A2E7BZ 发出的所有事件都将在 Angular 区域之外运行。 If you need to update any data bindings as a result of a scroll event, you have * to run the callback using `NgZone.run如果由于滚动事件而需要更新任何数据绑定,则必须 * 使用 `NgZone.run 运行回调

That's why you need to either explicitly call something that will trigger CD.这就是为什么您需要显式调用会触发 CD 的东西的原因。 In your case, changeDetectorRef.detectChanges() is enough !在您的情况下, changeDetectorRef.detectChanges()就足够了!

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

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