Angular2更改检测:ngOnChanges未针对嵌套对象触发

时间:2016-01-14 18:20:08

标签: angular

我知道我不是第一个提出这个问题的人,但我在之前的问题中找不到答案。我在一个组件中有这个

<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地图上标记的触发器。问题是当父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。当我改变变焦时,我确实看到了标记的变化。所以唯一的问题是我在输入数据发生变化时没有收到通知。

在父母身上,我有这条线。 (回想一下,变化反映在圈数中,但不反映在地图中)。

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

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

this.rawLapsData = this.main.data.TrainingCenterDatabase.Activities[0].Activity[0].Lap;

18 个答案:

答案 0 :(得分:112)

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

在更改检测期间,当Angular检查组件的输入属性以进行更改时,它会(基本上)使用===进行脏检查。对于数组,这意味着对数组引用(仅)进行脏检查。由于rawLapsData数组引用未更改,因此不会调用ngOnChanges()

我可以想到两种可能的解决方案:

  1. 实施ngDoCheck()并执行您自己的更改检测逻辑,以确定数组内容是否已更改。 (Lifecycle Hooks doc有an example。)

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

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

    在OP上重复一些评论:

      

    我仍然没有看到laps如何能够了解更改(当然它必须使用与ngOnChanges()本身相当的东西?)而map则不能。

    • laps组件中,您的代码/模板遍历lapsData数组中的每个条目,并显示内容,因此在显示的每条数据上都有Angular绑定。
    • 即使Angular未检测到组件输入属性的任何更改(使用===检查),它仍然(默认情况下)脏检查所有模板绑定。当其中任何一个发生变化时,Angular将更新DOM。这就是你所看到的。
    • maps组件可能在其模板中没有任何绑定到其lapsData输入属性,对吧?这可以解释其中的差异。

    请注意,两个组件中的lapsData和父组件中的rawLapsData都指向相同/一个数组。因此,即使Angular没有注意到lapsData输入属性的任何(引用)更改,组件“get”/看到任何数组内容更改,因为它们共享/引用该一个数组。我们不需要Angular来传播这些更改,就像我们使用基本类型(字符串,数字,布尔值)一样。但是对于原始类型,对值的任何更改都将始终触发ngOnChanges() - 这是您在答案/解决方案中利用的内容。

    正如您可能已经想到的那样,对象输入属性与数组输入属性具有相同的行为。

答案 1 :(得分:14)

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

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

我认为我更倾向于采用这种方法来实现你自己的ngDoCheck(),但也许像@GünterZöchbauer这样的人可以加入。

答案 2 :(得分:8)

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

  

每当您对其进行任何更改时,都会将新数组分配给rawLapsData   数组内容。然后将调用ngOnChanges()因为数组   (参考)将显示为更改

你可以像这样克隆数组的内容:

rawLapsData = rawLapsData.slice(0);

我提到这是因为

  

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

对我不起作用。我希望这会有所帮助。

答案 3 :(得分:6)

使用ChangeDetectorRef.detectChanges()告诉Angular在编辑嵌套对象时运行更改检测(它错过了脏检查)。

答案 4 :(得分:6)

如果数据来自外部库,您可能需要在zone: NgZone中运行数据更新语句。为此注入zone.run()。如果您已经在zone.run()内运行外部库的实例化,那么以后可能不需要Rounding of datetime Fractional Second Precision

答案 5 :(得分:3)

我的'黑客'解决方案是

   <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能够通过更简单的 object 基元类型检测更改。它不优雅,但它有效。

答案 6 :(得分:3)

对于数组,您可以这样做:

.ts文件(父组件)中,您要在其中更新rawLapsData的操作如下:

rawLapsData = somevalue; // change detection will not happen

解决方案:

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

ngOnChanges将在子组件中调用

答案 7 :(得分:2)

这是一个让我摆脱困境的黑客。

所以与OP类似的情况 - 我有一个嵌套的Angular组件需要传递给它的数据,但输入指向一个数组,如上所述,Angular没有看到更改,因为它不检查数组的内容。

所以要修复它我将数组转换为Angular的字符串来检测更改,然后在嵌套组件中我将字符串拆分(&#39;,&#39;)回到数组并且它的快乐时光试。

答案 8 :(得分:1)

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

import * as _ from 'lodash';

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

答案 9 :(得分:1)

我有2种解决方案来解决您的问题

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

答案 10 :(得分:0)

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

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

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

[wholeObj] = "obj"

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

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

所以你也可以检测子对象的变化。

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

答案 11 :(得分:0)

下面是将IterableDiffer与ngDoCheck结合使用的示例。如果您需要跟踪一段时间内的变化,则IterableDiffer特别有用,因为它可以让您执行诸如仅对添加/更改/删除的值进行迭代之类的事情。

一个简单的示例,它没有利用IterableDiffer的所有优点,但是可以正常工作并显示了原理:

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>

答案 12 :(得分:0)

在处理数据时,例如:

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

然后您应该使用它来检测更改:

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

由于角度ngOnchanges无法检测数组中的更改,因此对象必须分配一个新引用。每次都能工作!

答案 13 :(得分:0)

在我的情况下,ngOnChange没有捕获对象值的变化。响应api调用,修改了一些对象值。重新初始化对象解决了该问题,并导致ngOnChange在子组件中触发。

类似

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

答案 14 :(得分:0)

我必须为此创建一个hack-

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

答案 15 :(得分:0)

好的,所以我的解决方案是:

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

这触发了我ngOnChanges

答案 16 :(得分:0)

我已经尝试了这里提到的所有解决方案,但是由于某些原因,ngOnChanges()仍然没有为我解决。因此,在调用重新填充数组并且可以正常工作的服务之后,我用this.ngOnChanges()对其进行了调用。可能不会。整齐?一定不行。作品?是的!

答案 17 :(得分:0)

我偶然发现了同样的需求。我对此读了很多,所以这是我的主题。

如果要在推送时进行更改检测,那么在更改右侧对象的值时会拥有它。而且,如果您以某种方式删除对象,也将拥有它。

如前所述,使用changeDetectionStrategy.onPush

假设您使用changeDetectionStrategy.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,[=]符号对其进行新引用,每次需要时触发更改检测。简单易读:)

此致