哪个@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
还有其他吗?
我意识到这有点像一个支持请求,但是,再次强调,这里的意图是改进框架以对这些情况进行某种类型的警告,而不是什么都不做,就像现在这样。
5条答案
按热度按时间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
,因此更改后的对象本身将被接受为原样。然而,对象的属性仅在使用原始嵌入式视图上下文的嵌入视图中渲染,因此对对象的更改直到下一个变化检测周期才会可见,因为只有在那时才会创建/更新一个新的嵌入式视图。mpbci0fu2#
哦,我原以为ngFor使用
DefaultIterableDiffer
?n6lpvg4x3#
它确实如此,但这与
checkNoChanges
无关。Angular的核心绑定机制使用Object.is
来确定相等性;如果某事发生了变化,那么它将重新绑定输入并触发ngOnChanges
。对于NgForOf
来说,这意味着改变数组将重新绑定集合输入,然后NgForOf
使用IterableDiffer
对其进行差异分析。数组项的任何更改(取决于trackBy
)将触发嵌入式视图的创建/删除/更新。所有这些都发生在主要的变更检测运行中。在主要的变更检测运行完成后,Angular将运行其
checkNoChanges
阶段。这将使用devModeEqual
比较器重新评估绑定表达式,以及Object.is
。在此阶段,更改实际上永远不会写入相应的指令输入,因此NgForOf
及其IterableDiffer
在这段时间内永远不会看到已更改的数组。只有在随后触发的主要变更检测运行中,才会给NgForOf
提供新数组及其项目。ntjbwcob4#
那么,从这里开始,接下来是什么步骤?我不是不耐烦,只是好奇,最终的分级可能会在什么时候发生?
mzillmmw5#
Fwiw,新的
@for
块在此处描述的情况中触发NG0100。