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. Проблема в том, что 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 обновляется. В ngInit есть прослушиватель событий для изменения масштаба, который также вызывает this.drawmarkers. Когда я меняю масштаб, я действительно вижу изменение маркеров. Поэтому единственная проблема заключается в том, что я не получаю уведомление во время изменения входных данных.

В родительском я имею эту строку. (Напомним, что изменение отражается в кругах, но не на карте).

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

И обратите внимание, что this.rawLapsData сам является указателем на середину большого json-объекта

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

Ответ 1

rawLapsData продолжает указывать на тот же массив, даже если вы изменяете содержимое массива (например, добавляете элементы, удаляете элементы, изменяете элемент).

При обнаружении изменений, когда Angular проверяет свойства компонентов компонентов для изменения, он использует (по существу) === для грязной проверки. Для массивов это означает, что ссылки массива (только) грязно проверены. Поскольку rawLapsData массив rawLapsData не изменяется, ngOnChanges() не будет вызываться.

Я могу представить два возможных решения:

  1. ngDoCheck() и выполните собственную логику обнаружения изменений, чтобы определить, изменилось ли содержимое массива. (Например, пример документа Lifecycle Hooks.)

  2. Назначьте новый массив rawLapsData всякий раз, когда вы вносите какие-либо изменения в содержимое массива. Затем ngOnChanges(), потому что массив (ссылка) появится как изменение.

В вашем ответе вы пришли к другому решению.

Повторение некоторых комментариев здесь на OP:

Я все еще не вижу, как laps могут подхватить изменение (возможно, он должен использовать что-то, что соответствует самому ngOnChanges()?), А map не может.

  • В компоненте laps ваш код/​​шаблон lapsData каждую запись в массиве lapsData и отображает содержимое, поэтому на каждой части данных отображаются угловые привязки.
  • Даже когда Angular не обнаруживает никаких изменений в свойствах ввода компонента (используя === проверку), он по-прежнему (по умолчанию) грязный проверяет все привязки шаблонов. Когда какое-либо из этих изменений изменится, Angular обновит DOM. Это то, что вы видите.
  • Компонент maps вероятно, не имеет привязок в своем шаблоне к lapsData ввода lapsData, не так ли? Это объясняет разницу.

Обратите внимание, что lapsData в обоих компонентах и rawLapsData в родительском компоненте все указывают на один и тот же/один массив. Таким образом, хотя Angular не замечает никаких (ссылочных) изменений в lapsData ввода lapsData, компоненты "get"/видят содержимое любого массива, потому что все они разделяют/ссылаются на один массив. Нам не нужен Angular для распространения этих изменений, как и с примитивным типом (string, number, boolean). Но с примитивным типом любое изменение значения всегда ngOnChanges() бы ngOnChanges() - это то, что вы используете в своем ответе/решении.

Как вы, вероятно, уже выяснили, что свойства ввода объекта имеют то же поведение, что и свойства ввода массива.

Ответ 2

Не самый чистый подход, но вы можете просто клонировать объект каждый раз, когда вы меняете значение?

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

Я думаю, что я предпочел бы этот подход для реализации вашего собственного ngDoCheck(), но, возможно, кому-то понравится @GünterZöchbauer.

Ответ 3

В качестве дополнения к второму решению Марка Райкока

Назначьте новый массив rawLapsData всякий раз, когда вы вносите какие-либо изменения в содержимое массива. Затем вызывается ngOnChanges(), потому что массив (ссылка) появится как изменение

вы можете клонировать содержимое массива следующим образом:

rawLapsData = rawLapsData.slice(0);

Я упоминаю это, потому что

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

не работал у меня. Надеюсь, это поможет.

Ответ 4

Если данные поступают из внешней библиотеки, вам может потребоваться запустить инструкцию upate данных в zone.run(...). Inject zone: NgZone для этого. Если вы можете запустить экземпляр внешней библиотеки в zone.run() уже, то вам может не понадобиться zone.run() позже.

Ответ 5

Используйте ChangeDetectorRef.detectChanges(), чтобы сообщить Angular, чтобы запустить обнаружение изменений, когда вы редактируете вложенный объект (который он пропускает с его грязной проверкой).

Ответ 6

Мое решение "hack" -

   <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, и это дает другой шанс обнаружить изменение через более простой object примитивный тип. Он НЕ элегантный, но он работает.

Ответ 7

В случае массивов вы можете сделать это следующим образом:

В файле .ts (родительский компонент), в котором вы обновляете rawLapsData выполните следующие действия:

rawLapsData = somevalue; // change detection will not happen

Решение:

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

и ngOnChanges в дочернем компоненте

Ответ 8

Вот хак, который просто избавил меня от неприятностей с этим.

Итак, аналогичный сценарий для OP - у меня есть вложенный компонент Angular, которому нужны данные, переданные ему, но входные точки в массив, и, как упоминалось выше, Angular не видит изменить, поскольку он не проверяет содержимое массива.

Итак, чтобы исправить это, я преобразовываю массив в строку для Angular, чтобы обнаружить изменение, а затем во вложенном компоненте я снова разбиваю строку (',') на массив и его счастливые дни.

Ответ 9

Обнаружение изменений не срабатывает при изменении свойства объекта (включая вложенный объект). Одним из решений было бы переназначить новую ссылку на объект с помощью функции lodash clone().

import * as _ from 'lodash';

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

Ответ 10

Я наткнулся на такую же потребность. И я много читал об этом, вот моя медь по этому вопросу.

Если вы хотите, чтобы ваше обнаружение изменений было нажато, вы бы получили его, когда вы изменили значение объекта внутри справа? И вы также захотите, если каким-то образом вы удалите объекты.

Как уже говорилось, использование 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 возвращает тот же самый массив, а знак [=] делает новую ссылку на него, запуская обнаружение изменений каждый раз, когда вам это нужно. Легко и удобочитаемо :)

С Уважением,

Ответ 11

Я пробовал все решения, упомянутые здесь, но по какой-то причине ngOnChanges() все еще не срабатывал для меня. Поэтому я вызвал его с помощью this.ngOnChanges() после вызова службы, которая повторно заполняет мои массивы, и это сработало... правильно? возможно нет. Аккуратные? конечно нет. Работает? да!

Ответ 12

У меня есть 2 решения для решения вашей проблемы

  1. Используйте ngDoCheck для изменения данных object или нет.
  2. Назначить object новому адресу памяти по object = Object.create(object) из родительского компонента.

Ответ 13

хорошо, поэтому мое решение для этого было:

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

И это вызывает меня ngOnChanges