首页 文章

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

提问于
浏览
85

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

<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();
    }

Update: ngOnChanges无效,但看起来好像正在更新lapsData . 在ngInit中,缩放更改的事件侦听器也调用 this.drawmarkers . 当我改变变焦时,我确实看到了标记的变化 . 所以唯一的问题是我在输入数据发生变化时没有收到通知 .

在父母,我有这条线 . (回想一下,变化反映在圈数中,但不反映在 Map 中) .

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

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

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

13 回答

  • 3

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

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

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

  • 6

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

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

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

    rawLapsData = rawLapsData.slice(0);
    

    我提到这个是因为

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

    不适合我 . 我希望这有帮助 .

  • 0

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

  • 1

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

  • 102

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

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

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

    • 实现 ngDoCheck() 并执行您自己的更改检测逻辑以确定阵列内容是否已更改 . (Lifecycle Hooks doc有an example . )

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

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

    在OP上重复一些评论:

    我仍然没有看到圈子如何能够接受改变(当然它必须使用与ngOnChanges()本身相当的东西?)而map不能 .

    • laps 组件中,您的代码/模板遍历 lapsData 数组中的每个条目,并显示内容,因此在显示的每个数据上都有Angular绑定 .

    • 即使Angular没有't detect any changes to a component'的输入属性(使用 === 检查),它仍然(默认情况下)脏检查所有模板绑定 . 当其中任何一个发生变化时,Angular将更新DOM . 这就是你所看到的 .

    • maps 组件可能在其模板中没有任何绑定到其 lapsData 输入属性,对吧?这可以解释不同之处 .

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

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

  • 0

    我的'黑客'解决方案是

    <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能够通过更简单的对象基元类型检测更改 . 它不优雅,但它的工作原理 .

  • 3

    在阵列的情况下,您可以这样做:

    .ts 文件(父组件)中,您要更新 rawLapsData ,请执行以下操作:

    rawLapsData = somevalue; // change detection will not happen
    

    解:

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

    ngOnChanges 将在子组件中调用

  • 2

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

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

    所以要修复它,我将数组转换为Angular的字符串来检测更改,然后在嵌套组件中我将字符串拆分(',')回到数组并再次开心 .

  • 6

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

    import * as _ from 'lodash';
    
    this.foo = _.clone(this.foo);
    
  • 8

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

    • 使用 ngDoCheck 检测 object 数据是否已更改

    • 通过父组件的 object = Object.create(object)object 分配给新的内存地址 .

  • 14

    我偶然发现了同样的需求 . 我在这方面看了很多,所以这是关于这个主题的铜 .

    如果您希望在推送时进行更改检测,那么当您更改对象内部的值时,您会拥有它吗?如果不知何故,你也会删除对象 .

    如前所述,使用changeDetectionStrategy.onPush

    假设你使用changeDetectionStrategy.onPush创建了这个组件:

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

    然后你推动一个项目并触发变化检测:

    myCollection.push(anItem);
    refresh();
    

    或者你删除一个项目并触发更改检测:

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

    或者您更改项目的attrbibute值并触发更改检测:

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

    刷新内容:

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

    slice方法返回完全相同的Array,[=]符号对其进行新的引用,每次需要时触发更改检测 . 简单易读:)

    问候,

  • 0

    我已经尝试了这里提到的所有解决方案,但出于某种原因 ngOnChanges() 仍然没有为我开火 . 所以我在调用重新填充我的数组的服务之后用 this.ngOnChanges() 调用它并且它工作....正确吗?可能不是 . 整齐?一定不行 . 作品?是!

  • 1

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

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

    这触发了我的变化

相关问题