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 为旧数据。就这样继续下去……所以你总是后退一步。
嘿,我创建了一个演示来大规模演示问题。
更新: 我已经更新了演示,问题出在 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 为旧数据。就这样继续下去……所以你总是后退一步。