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中,数据以表格格式输出为 HTML,每当 rawLapsdata发生变化时,这种格式就会发生变化。

我的 map组件需要使用 ngOnChanges作为触发器来重新绘制 Google Map 上的标记。问题是,当 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正在更新。在 ngOnInit中有一个用于缩放更改的事件侦听器,该事件侦听器也调用 this.drawmarkers。当我改变变焦时,我确实看到了标记的变化。所以唯一的问题是,在输入数据发生变化时,我没有收到通知。

在父代中,我有这一行(回想一下,更改反映在圈中,但不反映在 map中)。

this.rawLapsData = deletePoints(this.rawLapsData, this.selectedTps);

请注意,this.rawLapsData本身是一个指向大型 json 对象中间的指针

this.rawLapsData = this.main.data.TrainingCenterDatabase.Activities[0].Activity[0].Lap;
223644 次浏览

如果数据来自外部库,则可能需要在 zone.run(...)中运行数据更新语句。为此注射 zone: NgZone。如果您已经可以在 zone.run()中运行外部库的实例化,那么以后可能就不需要 zone.run()了。

我的“黑客”解决方案是

   <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 同时发生更改,这为 map 提供了通过更简单的 对象基元类型检测更改的另一个机会。它不是优雅的,但它工作。

rawLapsData继续指向同一个数组,即使您修改了数组的内容(例如,添加项、删除项、更改项)。

在变化检测中,当 Angular 检查组件的输入属性是否有变化时,它使用(基本上) ===进行脏检查。对于数组,这意味着数组引用(仅)被脏检查。由于 rawLapsData数组引用没有改变,因此不会调用 ngOnChanges()

我能想到两种可能的解决办法:

  1. 实现 ngDoCheck()并执行您自己的变化检测逻辑来确定数组内容是否已更改。(生命周期钩子文档有 一个例子。)

  2. 每当对数组内容进行任何更改时,都将一个新数组分配给 rawLapsData。然后将调用 ngOnChanges(),因为数组(引用)将作为更改显示。

在你的回答中,你提出了另一个解决方案。

在这里重复一些关于 OP 的评论:

我仍然不明白 laps如何能够发现这种变化(它肯定使用了与 ngOnChanges()本身相当的东西吧?)而 map不能。

  • laps组件中,代码/模板循环遍历 lapsData数组中的每个条目,并显示内容,因此显示的每个数据片段都有角度绑定。
  • 即使 Angular 没有检测到组件输入属性的任何更改(使用 ===检查) ,它仍然(默认情况下)脏检查所有模板绑定。当任何这些变化,Angular 将更新 DOM。这就是你看到的。
  • maps组件的模板中可能没有对其 lapsData输入属性的任何绑定,对吗?这就能解释区别了。

注意,两个组件中的 lapsData和父组件中的 rawLapsData都指向同一个数组。因此,即使 Angular 没有注意到 lapsData输入属性的任何(引用)变化,组件“ get”/see 任何数组内容的变化,因为它们都共享/引用同一个数组。我们不需要 Angular 来传播这些变化,就像我们使用基元类型(string、 number、 boolean)一样。但是对于基元类型,对值的任何更改总是会触发 ngOnChanges() & nash; ,这是您在应答/解决方案中可以利用的内容。

现在您可能已经发现,对象输入属性的行为与数组输入属性的行为相同。

这个黑客刚刚帮我摆脱了麻烦。

所以一个类似于 OP 的场景-我有一个嵌套的 Angular 组件,它需要传递给它的数据,但是输入指向一个数组,如上所述,Angular 没有看到变化,因为它没有检查数组的内容。

为了解决这个问题,我将数组转换成一个字符串,以便 Angular 检测到变化,然后在嵌套组件中将字符串分割(’,’)为一个数组并再次享受它的快乐时光。

当你编辑一个嵌套对象时,使用 ChangeDetectorRef.detectChanges()告诉 Angular 运行一个变化检测。

虽然不是最干净的方法,但是您可以在每次更改值时克隆对象吗?

   rawLapsData = Object.assign({}, rawLapsData);

我想我更喜欢这种方法,而不是实现自己的 ngDoCheck(),但也许有人像@G ünterZöchbauer 可以插嘴。

作为 Mark Rajcok 第二个解决方案的延伸

方法进行任何更改时,将新数组分配给 rawLapsData 然后,ngOnChanges ()将被调用,因为数组 (参考文献)将作为变更出现

您可以像下面这样克隆数组的内容:

rawLapsData = rawLapsData.slice(0);

我提到这个是因为

RawLapsData = Object.sign ({} ,rawLapsData) ;

对我不起作用,我希望这能帮上忙。

更改对象(包括嵌套对象)的属性时不会触发变化检测。一种解决方案是使用‘ loash’clone ()函数重新分配一个新的对象引用。

import * as _ from 'lodash';


this.foo = _.clone(this.foo);

在更新 rawLapsData.ts文件(父组件)中,这样做:

rawLapsData = somevalue; // change detection will not happen

解决方案:

rawLapsData = {...somevalue}; //for Object, change detection will happen


rawLapsData = [...somevalue]; //for Array, change detection will happen

ngOnChanges将调用子组件

我偶然发现了同样的需求。我读了很多关于这方面的文章,这是我对这个问题的看法。

如果你想让你的变化检测按下,那么当你改变一个对象的内部值时,你就会得到它,对吗?如果你移除一些物体,你也会得到它。

如前所述,使用 changeDetectionStrategy. onPush

假设你已经创建了这个组件,其中包含 changeDetectionStrategies. onPush:

<component [collection]="myCollection"></component>

然后你按下一个按钮,触发变化检测:

myCollection.push(anItem);
refresh();

或者你会移除一个物品并触发变化检测:

myCollection.splice(0,1);
refresh();

或者你可以改变一个条目的属性值,然后触发变化检测:

myCollection[5].attribute = 'new value';
refresh();

更新内容:

refresh() : void {
this.myCollection = this.myCollection.slice();
}

Slice 方法返回完全相同的 Array,并且[ = ]符号对它进行了新的引用,每次需要它时就会触发变化检测。简单易读:)

问候,

我有两个办法来解决你的问题

  1. 使用 ngDoCheck检测 object数据是否更改
  2. 通过 object = Object.create(object)从父组件将 object分配给一个新的内存地址。

我的解决办法是:

this.arrayWeNeed.DoWhatWeNeedWithThisArray();
const tempArray = [...arrayWeNeed];
this.arrayWeNeed = [];
this.arrayWeNeed = tempArray;

这就触发了我的改变

我不得不创建一个黑客为它-

I created a Boolean Input variable and toggled it whenever array changed, which triggered change detection in the child component, hence achieving the purpose

在我的案例中,是对象值的变化,而 ngOnChange没有捕捉到这些变化。为响应 api 调用,修改了一些对象值。重新初始化对象修复了这个问题,并导致 ngOnChange在子组件中触发。

差不多

 this.pagingObj = new Paging(); //This line did the magic
this.pagingObj.pageNumber = response.PageNumber;

当你处理数据时,比如:

this.data.profiles[i].icon.url = '';

然后,您应该使用以检测更改:

let array = this.data.profiles.map(x => Object.assign({}, x)); // It will detect changes

由于角 ngOnchange 不能检测数组中的变化,那么我们必须为对象分配一个新的引用。屡试不爽!

下面是使用 不一样和 ngDoCheck 的示例。如果您需要跟踪随时间变化的变化,IterableDever 特别有用,因为它允许您迭代仅添加/更改/删除的值等事情。

一个简单的例子没有使用 IterableDever 的所有优点,但是它可以工作,并且展示了原理:

export class FeedbackMessagesComponent implements DoCheck {
@Input()
messages: UserFeedback[] = [];
// Example UserFeedback instance { message = 'Ooops', type = Notice }


@HostBinding('style.display')
display = 'none';


private _iterableDiffer: IterableDiffer<UserFeedback>;


constructor(private _iterableDiffers: IterableDiffers) {
this._iterableDiffer = this._iterableDiffers.find([]).create(null);
}


ngDoCheck(): void {
const changes = this._iterableDiffer.diff(this.messages);


if (changes) {
// Here you can do stuff like changes.forEachRemovedItem()


// We know contents of this.messages was changed so update visibility:
this.display = this.messages.length > 0 ? 'block' : 'none';
}
}
}

这将根据 myMessagesArray 计数自动显示/隐藏:

<app-feedback-messages
[messages]="myMessagesArray"
></app-feedback-messages>

假设您有一个嵌套对象,比如

var obj = {"parent": {"child": {....}}}

如果传递完整对象的引用,如

[wholeObj] = "obj"

在这种情况下,您无法检测子对象中的更改,因此为了克服这个问题,您还可以通过另一个属性传递子对象的引用,如

[wholeObj] = "obj" [childObj] = "obj.parent.child"

因此,您还可以检测来自子对象的更改。

ngOnChanges(changes: SimpleChanges) {
if (changes.childObj) {// your logic here}
}

这不是一个干净的解决方案,但是你可以使用:

rawLapsData = JSON.parse(JSON.stringify(rawLapsData))

只需在事件中发送一个对象或数组。永远不要把一个字符串或数字从父母传给子女。会成功的。 我有两个子组件和一个父组件。 这是我父母: 发送直接事件对象。

dataFrom1To2:any;
dataFrom2To1:any;
onBallPassEventFrom1(event:any){
this.dataFrom1To2 = event;
}


onBallPassEventFrom2(event:any){
this.dataFrom2To1 = event;
}

Html 是:

<div>I Am The Parent Component!</div>
<div>
<app-child1 (ballPassEventFrom1)="onBallPassEventFrom1($event)" [datafrom2]="dataFrom2To1"></app-child1>
</div>
<div>
<app-child2 [datafrom1]="dataFrom1To2" (ballPassEventFrom2)="onBallPassEventFrom2($event)"></app-child2>
</div>