Angular2変更検出:ネストされたオブジェクトに対してngOnChangesが起動しない


129

私はこれについて最初に尋ねるのではないことを知っていますが、前の質問で答えを見つけることができません。これは1つのコンポーネントにあります

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

コードには、データの更新方法やデータの種類は表示されません。新しいインスタンスが割り当てられていますか、それとも値のプロパティが変更されていますか?
ギュンターZöchbauer

@GünterZöchbauer親コンポーネントから行を追加しました
Simon H

私はこの行をラップzone.run(...)することでそれを行うべきだと思います。
ギュンターZöchbauer

1
配列(参照)は変更ngOnChanges()されないため、呼び出されません。ngDoCheck()独自のロジックを使用および実装して、配列の内容が変更されたかどうかを判断できます。 lapsDataと同じ配列への参照を持っているため、更新されrawLapsDataます。
Mark Rajcok、2016年

1
1)lapsコンポーネントでは、コード/テンプレートがlapsData配列の各エントリをループして内容を表示するため、表示される各データにAngularバインディングがあります。2)Angularがコンポーネントの入力プロパティの変更(参照チェック)を検出しなくても、(デフォルトで)すべてのテンプレートバインディングをチェックします。これがラップが変化に対応する方法です。3)マップコンポーネントのテンプレートには、lapsData入力プロパティへのバインディングが含まれていない可能性があります。それが違いを説明するでしょう。
Mark Rajcok

回答:


153

rawLapsData 配列の内容を変更しても(たとえば、アイテムの追加、アイテムの削除、アイテムの変更など)、同じ配列を指し続けます。

変更検出中に、Angularがコンポーネントの入力プロパティの変更をチェックするとき===、ダーティチェックに(本質的に)使用します。配列の場合、これは配列参照(のみ)がダーティチェックされることを意味します。以来、rawLapsData配列参照が変更されていない、ngOnChanges()呼び出されません。

私は2つの可能な解決策を考えることができます:

  1. ngDoCheck()独自の変更検出ロジックを実装して実行し、配列の内容が変更されたかどうかを判断します。(ライフサイクルフックのドキュメントにあります。)

  2. rawLapsData配列の内容を変更するたびに、新しい配列を割り当てます。次にngOnChanges()、配列(参照)が変更として表示されるため、呼び出されます。

あなたの答えでは、あなたは別の解決策を思いつきました。

ここでOPに関するコメントを繰り返します。

どうすれlapsば変更を取得できるのかまだわかりません(確かにそれと同等のものを使用している必要がありますngOnChanges()か?)mapできません。

  • lapsコンポーネントコード/テンプレート内の各エントリをループlapsData配列、及び表示内容ので、表示されているデータの各部分に角度バインディングがあります。
  • Angularがコンポーネントの入力プロパティの変更を(===チェックを使用して)検出しない場合でも、(デフォルトでは)すべてのテンプレートバインディングをダーティチェックします。これらのいずれかが変更されると、AngularはDOMを更新します。それはあなたが見ているものです。
  • mapsコンポーネントは、おそらくそのへのテンプレートのいずれかのバインディングはありませんlapsData右、入力プロパティを?それが違いを説明するでしょう。

lapsData両方のコンポーネントとrawLapsData親コンポーネントで、すべて同じ/ 1つの配列を指していることに注意してください。したがって、AngularはlapsData入力プロパティへの(参照)変更に気付かなくても、コンポーネントはすべて1つの配列を共有/参照するため、配列の内容を「取得」/表示します。これらの変更を反映するために、プリミティブ型(文字列、数値、ブール値)の場合のように、Angularは必要ありません。しかし、プリミティブ型では、値の変更は常にトリガーngOnChanges()になります。これは、回答/ソリューションで活用するものです。

おそらく今までに理解したように、オブジェクト入力プロパティは配列入力プロパティと同じ動作をします。


はい、私は深くネストされたオブジェクトを変異させていたので、Angularが構造の変化を見つけるのを難しくしました。変更するのは好みではありませんが、これは変換されたXMLであり、最後にもう一度xmlを再作成したいので、周囲のデータを失うわけにはいきません
Simon H

7
@SimonH、「Angularが構造内の変更を特定するのは難しい」-明確にするために、Angularは配列または変更用のオブジェクトである入力プロパティの内部を見さえしません。値が変更されたかどうかを確認するだけです。オブジェクトと配列の場合、値は参照です。プリミティブ型の場合、値は...値です。(私がすべての専門用語をまっすぐに持っているかどうかは
わかり

11
すばらしい答えです。Angular2チームは、変更検出の内部に関する詳細で信頼できるドキュメントを必死に公開する必要があります。

doCheckで機能を実行すると、私の場合、doチェックが何度も呼び出されます。他の方法を教えてください。
Mr_Perfect 2017

@MarkRajcokあなたは私がこの問題を解決するために助けを喜ばできstackoverflow.com/questions/50166996/...
Nikson

27

最もクリーンなアプローチではありませんが、値を変更するたびにオブジェクトを複製できますか?

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

私は自分で実装するよりもこのアプローチを好むと思いますngDoCheck()が、@GünterZöchbauerのような誰かが介入することができます。


ターゲットとなるブラウザーが<Object.assign()>をサポートするかどうかわからない場合は、json文字列に文字列化し、解析してjsonに戻すこともできます。これにより、新しいオブジェクトも作成されます...
Guntram

1
@Guntramまたはポリフィル?
デビッド

12

配列の場合、次のようにできます。

.ts、ファイル(親コンポーネント)あなたが更新されている場合rawLapsData、このようにそれを実行します。

rawLapsData = somevalue; // change detection will not happen

解決:

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

そして、ngOnChanges子コンポーネントで呼び出されます


10

Mark Rajcokの2番目のソリューションの拡張として

配列の内容を変更するたびに、新しい配列をrawLapsDataに割り当てます。次に、配列(参照)が変更として表示されるため、ngOnChanges()が呼び出されます

次のように配列の内容を複製できます。

rawLapsData = rawLapsData.slice(0);

私はこれについて言及しています

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

うまくいきませんでした。これがお役に立てば幸いです。


8

データが外部ライブラリからのものである場合は、内でデータ更新ステートメントを実行する必要がありますzone.run(...)。これを注入zone: NgZoneします。外部ライブラリのインスタンス化をzone.run()すでに実行できる場合は、zone.run()後で必要になることはありません。


OPへのコメントで述べたように、変更は外部ではなく、jsonオブジェクト内の深いものでした
Simon H

1
あなたの答えが言うように、Angular2のようなものを同期させるために何かを実行する必要があります、angular1のようでした$scope.$applyか?
Pankaj Parkar

1
Angularの外部から何かが開始された場合、ゾーンでパッチされたAPIは使用されず、Angularは変更の可能性について通知を受けません。はい、zone.runに似てい$scope.applyます。
ギュンターZöchbauer

6

ChangeDetectorRef.detectChanges()入れ子になったオブジェクトを編集するときに、変更の検出を実行するようにAngularに指示するために使用します(ダーティチェックで失敗する)。


しかし、どうやって?これを使用しようとしています。親が2つの方法でバインドされた[(Collection)]で新しいアイテムをプッシュした後、子でchangeDetectionをトリガーしたいと思います。
2018年

問題は、変更検出が行われないことではありませんが、その変更検出は変更を検出しません。だからこれは解決策ではないようです!なぜこの回答は5つの賛成票を得たのですか?
Shahryar Saljoughi

5

あなたの問題を解決する2つの解決策があります

  1. ngDoCheck検出に使用object変更されたデータの
  2. 親コンポーネントからobject新しいメモリアドレスに割り当てobject = Object.create(object)ます。

2
Object.create(object)とObject.assign({}、object)の間に顕著な違いはありますか?
Daniel Galarza

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と同時に変更されます。これにより、より単純なオブジェクトプリミティブタイプを介して変更を検出する機会がマップに与えられます。エレガントではありませんが、機能します。


特に中規模/大規模アプリで、テンプレート構文のさまざまなコンポーネントのすべての変更を追跡するのは難しいと思います。通常、私はデータを渡すために共有イベントエミッターとサブスクリプションを使用するか(クイックソリューション)、またはこのために(Rx.Subjectを介して)Reduxパターンを実装します(計画する時間がある場合)...
Sasxa

3

オブジェクト(ネストされたオブジェクトを含む)のプロパティを変更しても、変更の検出はトリガーされません。1つの解決策は、 'lodash' clone()関数を使用して新しいオブジェクト参照を再割り当てすることです。

import * as _ from 'lodash';

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

2

これが私をこの問題でトラブルから解放したハックです。

したがって、OPと同様のシナリオ-データを渡す必要があるネストされたAngularコンポーネントがありますが、入力は配列を指しており、前述のように、Angularは変更を確認しないため、変更を認識しません。配列の内容。

修正するには、Angularが変更を検出できるように配列を文字列に変換し、ネストされたコンポーネントで文字列をsplit( '、')して配列に戻し、再び幸せな日々を過ごします。


1
おい!array.slice(0)のアプローチははるかにクリーンです。
クリスヘインズ

2

私は同じ必要に出会いました。そして、私はこれについてたくさん読んだので、ここに私の主題の銅があります。

プッシュで変更の検出が必要な場合は、オブジェクトの値を内部で変更したときにそれが表示されますか?そして、どういうわけか、あなたがオブジェクトを削除した場合にもあなたはそれを持っているでしょう。

すでに述べたように、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メソッドはまったく同じ配列を返し、[=]記号はその配列への新しい参照を作成し、必要になるたびに変更検出をトリガーします。簡単で読みやすい:)

よろしく、


2

私はここで言及されたすべての解決策を試しましたが、何らかの理由でngOnChanges()まだ私には発砲しませんでした。だから私はthis.ngOnChanges()私の配列を再投入するサービスを呼び出した後にそれを呼び出しました、そしてそれはうまくいきました...正しいですか?おそらく違います。きちんと?地獄いいえ。機能しますか?はい!


1

わかりましたので、これに対する私の解決策は:

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

そして、これはmengOnChangesをトリガーします


0

私はそれのためのハックを作らなければなりませんでした-

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

0

私の場合、それはngOnChangeキャプチャしていないオブジェクト値の変更でした。いくつかのオブジェクト値は、API呼び出しの応答として変更されます。オブジェクトを再初期化すると問題が修正され、ngOnChangeれ、が子コンポーネントでトリガーされました。

何かのようなもの

 this.pagingObj = new Paging(); //This line did the magic
 this.pagingObj.pageNumber = response.PageNumber;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.