Angular : NgIf 的变更检测问题

Angular : Change Detection Problem With NgIf

嘿,我创建了一个演示来大规模演示问题。

更新: 我已经更新了演示,问题出在 ngIf!!

这是演示: https://stackblitz.com/edit/angular-ivy-bac4pe?file=src%2Fapp%2Flist%2Flist.component.ts

这就是问题所在: 我们不仅将指令用作组件行为的扩展,还用作组件的外部 API。 我们有一个列表组件,它假设用分页显示列表的当前项目。 我们有一个分页器指令,他的工作是为组件公开分页 Api。 我们有一个分页服务,它根据数据和页面大小执行实际的分页(分页方法)。

我发生了一个非常奇怪的行为: 有一个 setInterval 就像轮询一样 - 每 5 秒更新一次数据。 当我尝试通过指令->服务->更新当前项目列表时 这些项目没有得到更新,即使我调用了 markforCheck ,因为我正在使用 Push Strategy 来提高性能。我不想调用 detectChanges,因为这是一种不好的做法。

当我尝试通过服务->组件更新当前项目列表时 使用 markForCheck 一切正常。

如果有人能详细解释为什么会出现这种现象以及如何解决这个问题,我将很高兴。

快速回答是使用 [hidden] 而不是 *ngIf。

但我仍然不知道为什么我需要更喜欢 [hidden] 而不是 *ngIf。

*ngIf[hidden] 标签的最大区别在于,当使用 *ngIf 时,根本不会加载数据。使用 [hidden] 标签时,数据已加载但未显示。

如果要经常更改show/hide状态,最好使用[hidden]

发生的事情是一系列原因。

首先,请注意 this.cdr.markForCheck() 不会 运行 更改检测,而是将其祖先标记为需要 运行 更改检测。 下一次 在任何地方更改检测运行,它也会运行 那些被标记的组件。

第二个也是更重要的是 *ngIf 结构。当更改发生时 *ngIf 将首先通知并执行以下操作:(github)

    @Input()
      set ngIf(condition: T) {
        this._context.$implicit = this._context.ngIf = condition;
        this._updateView();
      }
      // ...
      private _updateView() {
        if (this._context.$implicit) {
          if (!this._thenViewRef) {
            this._viewContainer.clear();
            this._elseViewRef = null;
            if (this._thenTemplateRef) {
              this._thenViewRef =
                  this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);
            }
          }
        } else {
          if (!this._elseViewRef) {
            this._viewContainer.clear();
            this._thenViewRef = null;
            if (this._elseTemplateRef) {
              this._elseViewRef =
                  this._viewContainer.createEmbeddedView(this._elseTemplateRef, this._context);
            }
          }
        }
      }

如你所见:

this._viewContainer.createEmbeddedView(this._thenTemplateRef, this._context);

这意味着它需要 dom 并重新生成它!在此过程中,您将丢失要渲染的更改数据。

下次父组件数据发生变化并触发其变化检测时,也会检查子组件中的变化,因为您在上一次调用 markForCheck() 时保留了此检查,再次 *ngIf 做出反应在其容器之前更改,因此它获取并替换 dom 为旧数据。就这样继续下去……所以你总是后退一步。