170

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

<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 地图上的标记。问题是当父级发生更改ngOnChanges时不会触发。rawLapsData我能做些什么?

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;
4

18 回答 18

195

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

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

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

  1. 实现ngDoCheck()并执行您自己的更改检测逻辑以确定数组内容是否已更改。(Lifecycle Hooks 文档有一个例子。)

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

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

在此处重复一些关于 OP 的评论:

我仍然看不到如何laps接受更改(肯定它必须使用与ngOnChanges()自身等效的东西?)而map不能。

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

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

正如您现在可能发现的那样,对象输入属性与数组输入属性具有相同的行为。

于 2016-01-14T20:37:14.870 回答
38

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

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

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

于 2017-07-03T15:18:59.330 回答
20

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

rawLapsData = somevalue; // change detection will not happen

解决方案:

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

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

并将ngOnChanges调用子组件

于 2018-06-20T11:30:13.733 回答
12

作为 Mark Rajcok 的第二个解决方案的扩展

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

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

rawLapsData = rawLapsData.slice(0);

我提到这个是因为

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

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

于 2017-09-29T15:12:20.857 回答
9

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

于 2016-01-14T18:28:57.223 回答
6

用于ChangeDetectorRef.detectChanges()告诉 Angular 在您编辑嵌套对象时运行更改检测(它会因脏检查而丢失)。

于 2017-05-08T16:35:51.510 回答
6

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

import * as _ from 'lodash';

this.foo = _.clone(this.foo);
于 2018-04-11T06:21:28.237 回答
5

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

  1. 用于ngDoCheck检测object数据是否更改
  2. 从父组件分配object给新的内存地址。object = Object.create(object)
于 2018-11-28T04:52:47.747 回答
3

我偶然发现了同样的需求。我在这方面读了很多,这是我关于这个主题的铜版。

如果您希望在推送时检测更改,那么当您更改内部对象的值时您会拥有它吗?如果以某种方式删除对象,您也会拥有它。

如前所述,使用 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,并且 [ = ] 符号对其进行了新引用,每次需要时都会触发更改检测。简单易读:)

问候,

于 2018-10-04T12:49:30.797 回答
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 有另一个机会通过更简单的对象原始类型检测更改。它并不优雅,但它确实有效。

于 2016-01-14T18:58:21.823 回答
2

这是一个让我摆脱这个麻烦的hack。

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

因此,为了修复它,我将数组转换为字符串以供 Angular 检测更改,然后在嵌套组件中,我将字符串拆分(',')回数组并再次获得快乐。

于 2016-10-02T22:40:06.970 回答
1

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

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

这触发了我 ngOnChanges

于 2018-12-06T12:28:04.930 回答
0

不是一个干净的解决方案,但您可以通过以下方式触发检测:

rawLapsData = JSON.parse(JSON.stringify(rawLapsData))
于 2021-08-24T18:09:41.110 回答
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>

于 2020-12-04T13:40:25.343 回答
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
于 2020-03-13T08:59:25.987 回答
0

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

就像是

 this.pagingObj = new Paging(); //This line did the magic
 this.pagingObj.pageNumber = response.PageNumber;
于 2020-07-15T10:25:03.330 回答
0

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

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

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

[wholeObj] = "obj"

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

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

因此,您也可以检测子对象的变化。

ngOnChanges(changes: SimpleChanges) { 
    if (changes.childObj) {// your logic here}
}
于 2021-01-24T17:45:05.283 回答
0

当您操作以下数据时:

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

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

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

由于角度 ngOnchanges 无法检测到数组、对象的变化,因此我们必须分配一个新的引用。每次都有效!

于 2020-08-20T19:17:15.593 回答