Angular2更改检测:ngOnChanges不会触发嵌套对象
我知道我不是第一个问这个问题的人,但是我在前面的问题中找不到答案。 我有这个在一个组件
<div class="col-sm-5"> <laps [lapsData]="rawLapsData" [selectedTps]="selectedTps" (lapsHandler)="lapsHandler($event)"> </laps> </div> <map [lapsData]="rawLapsData" class="col-sm-7"> </map>
在控制器中, rawLapsdata
会不时发生变异。
在laps
,数据以表格forms作为HTML输出。 每当rawLapsdata
改变时, rawLapsdata
改变。
我的map
组件需要使用ngOnChanges
作为触发器来重绘Google Map上的标记。 问题是,在父rawLapsData
更改rawLapsData时,ngOnChanges不会触发。 我能做什么?
import {Component, Input, OnInit, OnChanges, SimpleChange} from 'angular2/core'; @Component({ selector: 'map', templateUrl: './components/edMap/edMap.html', styleUrls: ['./components/edMap/edMap.css'] }) export class MapCmp implements OnInit, OnChanges { @Input() lapsData: any; map: google.maps.Map; ngOnInit() { ... } ngOnChanges(changes: { [propName: string]: SimpleChange }) { console.log('ngOnChanges = ', changes['lapsData']); if (this.map) this.drawMarkers(); }
更新: ngOnChanges不工作,但它看起来好像lapsData正在更新。 在ngInit是一个缩放更改的事件监听器,也调用this.drawmarkers
。 当我改变缩放,我确实看到标记的变化。 所以唯一的问题是我在input数据改变的时候没有得到通知。
在父母,我有这条线。 (回想一下,变化反映在圈,但不是在地图上)。
this.rawLapsData = deletePoints(this.rawLapsData, this.selectedTps);
并注意this.rawLapsData
本身是一个指向大型json对象中间的指针
this.rawLapsData = this.main.data.TrainingCenterDatabase.Activities[0].Activity[0].Lap;
即使您修改数组的内容(例如,添加项目,移除项目,更改项目), rawLapsData
继续指向相同的数组。
在更改检测期间,当Angular检查组件的input属性以进行更改时,它使用(本质上) ===
进行脏检查。 对于数组来说,这意味着数组引用(仅)被脏检查。 由于rawLapsData
数组引用不会更改, ngOnChanges()
不会调用ngOnChanges()
。
我可以想到两个可能的解决scheme:
-
实现
ngDoCheck()
并执行你自己的变化检测逻辑来确定数组内容是否已经改变。 (Lifecycle Hooks文档有一个例子 。) -
无论何时对数组内容进行任何更改,都可以将新数组分配给
rawLapsData
。 然后ngOnChanges()
将被调用,因为数组(参考)将显示为一个更改。
在你的回答中,你提出了另一个解决scheme。
在OP上重复一些评论:
我还没有看到如何
laps
可以采取的变化(当然,它必须使用相当于ngOnChanges()
本身?),而map
不能。
- 在
laps
组件中,你的代码/模板在lapsData
数组中的每个条目上循环,并显示内容,所以在显示的每一条数据上都有Angular绑定。 - 即使Angular没有检测到组件input属性的任何改变(使用
===
检查),它仍然(默认情况下)脏检查所有的模板绑定。 当这些变化中的任何一个,Angular会更新DOM。 这就是你所看到的。 -
maps
组件可能在其模板中没有绑定到lapsData
input属性,对吧? 这将解释不同之处。
请注意,组件中的rawLapsData
和父组件中的rawLapsData
都指向相同的/一个数组。 所以即使Angular没有注意到lapsData
input属性的任何(引用)变化,组件“获取”/看到任何数组内容的变化,因为他们都共享/引用一个数组。 我们不需要Angular来传播这些变化,就像我们用原始types(string,number,boolean)一样。 但是对于一个原始types,任何对该值的改变总是会触发ngOnChanges()
– 这是你在你的答案/解决scheme中利用的东西。
正如你现在可能已经发现对象input属性具有与数组input属性相同的行为。
如果数据来自外部库,则可能需要在zone.run(...)
内运行数据upate语句。 注入zone: NgZone
为此。 如果可以在zone.run()
运行外部库的实例化,那么稍后可能不需要zone.run()
。
不是最干净的方法,但每次更改值时都可以克隆该对象?
rawLapsData = Object.assign({}, rawLapsData);
我想我会更喜欢这种方法来实现你自己的ngDoCheck()
但也许像@GünterZöchbauer可能会有人参加。
我的'黑客'解决scheme是
<div class="col-sm-5"> <laps [lapsData]="rawLapsData" [selectedTps]="selectedTps" (lapsHandler)="lapsHandler($event)"> </laps> </div> <map [lapsData]="rawLapsData" [selectedTps]="selectedTps" // <-------- class="col-sm-7"> </map>
selectedTps与rawLapsData同时发生更改,并使映射有更多机会通过更简单的对象基元types来检测更改。 这不是优雅,但它的作品。
这是一个黑客,让我摆脱这个麻烦。
因此,类似的情况下的OP – 我有一个嵌套的Angular组件需要数据传递给它,但input指向一个数组,如上所述,Angular没有看到一个变化,因为它不检查数组的内容。
所以要解决这个问题,我把数组转换为Angular的string来检测一个变化,然后在嵌套的组件中,我把string分割(',')到一个数组,然后又重新开始。
使用ChangeDetectorRef.detectChanges()
来告诉Angular在编辑一个嵌套的对象时(它错过了脏的检查)运行一个更改检测。
作为Mark Rajcok第二个解决scheme的扩展
无论何时对数组内容进行任何更改,都可以将新数组分配给rawLapsData。 然后ngOnChanges()将被调用,因为数组(引用)将作为一个改变出现
你可以像这样克隆数组的内容:
rawLapsData = rawLapsData.slice(0);
我提到这是因为
rawLapsData = Object.assign({},rawLapsData);
没有为我工作。 我希望这有帮助。