angular 意外的抑制(或未检测到)ExpressionChangedAfterItHasBeenCheckedError

zpgglvta  于 4个月前  发布在  Angular
关注(0)|答案(5)|浏览(50)

哪个@angular/*包是bug的来源?

core

这是一个回归吗?

描述

在开发过程中,我遇到了一些相对复杂的变更检测情况,我觉得Angular可能没有足够地正确标记它。我不确定这是否是一个bug(也许不是),也许它应该只是一个改进。话虽如此,我仍然想了解发生了什么,因为我浪费了很多时间在一个完全错误的地方寻找。
在链接中提供的示例中,当你点击按钮时,一个新组件出现。(为这个例子伪造的目的是)让它更新上面的两个属性(通过将一个新值推入位于子和父之间共享服务内的可观察对象中,然后父订阅它)-但它没有发生。当你再次点击它时,你终于看到了它(下一个变更检测触发,显然)。
经过漫长而艰苦的过程,现在我知道这里出了什么问题,以及如何根据变更检测周期、子更新父的DOM在同一变更检测宏任务中等来修复它。我不知道的是:为什么错误没有被抛出,当它显然是一个在父DOM更新后的同一tick中推送了新值的情况(这显然由值第一次不更新所证明)?
你可以通过在HelloComponent中推送两个对象或将空数组推送到父中的observable来轻松影响这一点。然后,你会(如预期的那样)看到标准的ExpressionChangedAfterItHasBeenCheckedError错误。
这仍然是用一个新的引用覆盖一个新数组,那么在这里幕后是如何进行比较的?为什么表达式(ngFor在这种情况下,在父元素中)似乎被评估为“相同的值”,因此不会抛出错误,即使它显然包含一个带有另一个具有不同值的属性的新数组?是因为ngFor在这个阶段插值为字符串(或者使用其他简化方法,例如仅检查数组长度、通过浅值比较等)吗?
看起来错误应该在这里被抛出。它可能是“按预期工作的”,但“预期”在没有警告的情况下是令人困惑的。

请提供一个最小重现错误的链接

https://stackblitz.com/edit/angular-jkshfv?file=src%2Fapp%2Fhello.component.ts

请提供您看到的异常或错误

It's one of these unusual cases, where I would actually like to see an error, but I don't see it :)

请提供您发现此bug的环境(运行 ng version )

Angular CLI: 12.1.0
Node: 14.17.4
Package Manager: npm 8.1.3
OS: darwin x64

Angular: 12.1.0
... animations, cdk, cli, common, compiler, compiler-cli, core
... forms, language-service, platform-browser
... platform-browser-dynamic, router

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1201.0
@angular-devkit/build-angular   12.1.0
@angular-devkit/core            12.1.0
@angular-devkit/schematics      12.1.0
@schematics/angular             12.1.0
rxjs                            6.6.3
typescript                      4.3.4

还有其他吗?

我意识到这有点像一个支持请求,但是,再次强调,这里的意图是改进框架以对这些情况进行某种类型的警告,而不是什么都不做,就像现在这样。

bq3bfh9z

bq3bfh9z1#

这是一个有趣的案例。devModeEqual检查接受任何对象作为相等:
angular/packages/core/src/change_detection/change_detection_util.ts
第11行到第25行
| | exportfunctiondevModeEqual(a: any,b: any): boolean{ |
| | constisListLikeIterableA=isListLikeIterable(a); |
| | constisListLikeIterableB=isListLikeIterable(b); |
| | if(isListLikeIterableA&&isListLikeIterableB){ |
| | returnareIterablesEqual(a,b,devModeEqual); |
| | }else{ |
| | constisAObject=a&&(typeofa==='object'||typeofa==='function'); |
| | constisBObject=b&&(typeofb==='object'||typeofb==='function'); |
| | if(!isListLikeIterableA&&isAObject&&!isListLikeIterableB&&isBObject){ |
| | returntrue; |
| | }else{ |
| | returnObject.is(a,b); |
| | } |
| | } |
| | } |
我猜想这是在假设使用它的原始属性中的任何一个(子)模板将在某些地方报告一个ExpressionChangedAfterItHasBeenCheckedError,因此更改后的对象本身将被接受为原样。然而,对象的属性仅在使用原始嵌入式视图上下文的嵌入视图中渲染,因此对对象的更改直到下一个变化检测周期才会可见,因为只有在那时才会创建/更新一个新的嵌入式视图。

mpbci0fu

mpbci0fu2#

哦,我原以为ngFor使用DefaultIterableDiffer?

n6lpvg4x

n6lpvg4x3#

它确实如此,但这与 checkNoChanges 无关。Angular的核心绑定机制使用 Object.is 来确定相等性;如果某事发生了变化,那么它将重新绑定输入并触发 ngOnChanges 。对于 NgForOf 来说,这意味着改变数组将重新绑定集合输入,然后 NgForOf 使用 IterableDiffer 对其进行差异分析。数组项的任何更改(取决于 trackBy )将触发嵌入式视图的创建/删除/更新。所有这些都发生在主要的变更检测运行中。
在主要的变更检测运行完成后,Angular将运行其 checkNoChanges 阶段。这将使用 devModeEqual 比较器重新评估绑定表达式,以及 Object.is 。在此阶段,更改实际上永远不会写入相应的指令输入,因此 NgForOf 及其 IterableDiffer 在这段时间内永远不会看到已更改的数组。只有在随后触发的主要变更检测运行中,才会给 NgForOf 提供新数组及其项目。

ntjbwcob

ntjbwcob4#

那么,从这里开始,接下来是什么步骤?我不是不耐烦,只是好奇,最终的分级可能会在什么时候发生?

mzillmmw

mzillmmw5#

Fwiw,新的@for块在此处描述的情况中触发NG0100。

相关问题