Angular2 değişiklik tespiti: ngOnChanges, yuvalanmış nesne için ateşlemiyor


129

Bunu ilk soran ben olmadığımı biliyorum, ancak önceki sorularda bir cevap bulamıyorum. Bunu tek bileşenim var

<div class="col-sm-5">
    <laps
        [lapsData]="rawLapsData"
        [selectedTps]="selectedTps"
        (lapsHandler)="lapsHandler($event)">
    </laps>
</div>

<map
    [lapsData]="rawLapsData"
    class="col-sm-7">
</map>

Denetleyicide rawLapsdatazaman zaman mutasyona uğrar.

İçinde laps, veriler tablo biçiminde HTML olarak çıkarılır. Bu her rawLapsdatadeğiştiğinde değişir.

Benim mapbileşen ihtiyaçları kullanmak ngOnChangesbir Google Haritası üzerinde işaretleri yeniden çizmek için bir tetikleyici olarak. Sorun, ebeveynde rawLapsDatadeğişiklik olduğunda ngOnChanges'ın tetiklenmemesidir . Ne yapabilirim?

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

Güncelleme: ngOnChanges çalışmıyor, ancak lapsData güncelleniyor gibi görünüyor. NgInit'te, aynı zamanda çağıran yakınlaştırma değişiklikleri için bir olay dinleyicisi vardır this.drawmarkers. Yakınlaştırmayı değiştirdiğimde, işaretçilerde gerçekten bir değişiklik görüyorum. Yani tek sorun, giriş verileri değiştiğinde bildirim almamam.

Ebeveynde bu satır var. (Değişikliğin turlara yansıdığını ancak haritaya yansıtılmadığını hatırlayın).

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

Ve bunun this.rawLapsDatakendisi büyük bir json nesnesinin ortasına bir işaretçi olduğunu unutmayın.

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

Kodunuz, verilerin nasıl güncellendiğini veya verilerin ne tür olduğunu göstermez. Yeni bir örnek atandı mı yoksa değiştirilen değerin yalnızca bir özelliği mi?
Günter Zöchbauer

@ GünterZöchbauer Ana bileşenden satır ekledim
Simon H

Sanırım bu satırı sarmak zone.run(...)o zaman yapmalı.
Günter Zöchbauer

1
Diziniz (referansınız) değişmiyor, bu yüzden ngOnChanges()çağrılmayacak. ngDoCheck()Dizi içeriklerinin değişip değişmediğini belirlemek için kendi mantığınızı kullanabilir ve uygulayabilirsiniz. lapsDataile aynı diziye bir başvuru olduğu için güncellenir rawLapsData.
Mark Rajcok

1
1) Tur bileşeninde, kodunuz / şablonunuz lapsData dizisindeki her giriş üzerinde döngü oluşturur ve içeriği görüntüler, böylece görüntülenen her veri parçası üzerinde Açısal bağlar olur. 2) Angular, bir bileşenin girdi özelliklerinde herhangi bir değişiklik (referans kontrolü) tespit etmese bile, yine de (varsayılan olarak) tüm şablon bağlamalarını kontrol eder. Tur değişiklikleri bu şekilde toparlıyor. 3) Haritalar bileşeninin muhtemelen şablonunda lapsData input özelliğine herhangi bir bağlantısı yoktur, değil mi? Bu farkı açıklar.
Mark Rajcok

Yanıtlar:


154

rawLapsData dizinin içeriğini değiştirseniz bile aynı diziyi göstermeye devam eder (örneğin, öğe ekleme, öğe kaldırma, bir öğeyi değiştirme).

Değişiklik tespiti sırasında, Angular bileşenlerin girdi özelliklerini değişiklik için kontrol ettiğinde, ===kirli kontrol için (esasen) kullanır . Diziler için bu, dizi referanslarının (yalnızca) kirli olarak kontrol edildiği anlamına gelir. Bu yana rawLapsDatabir dizi referans değişmemektedir, ngOnChanges()çağrılmayacaktır.

İki olası çözüm düşünebilirim:

  1. ngDoCheck()Dizi içeriklerinin değişip değişmediğini belirlemek için kendi değişiklik algılama mantığınızı uygulayın ve gerçekleştirin. (Yaşam Döngüsü Kancaları belgesinde bir örnek vardır .)

  2. rawLapsDataDizi içeriğinde herhangi bir değişiklik yaptığınızda yeni bir dizi atayın . Ardından ngOnChanges(), dizi (başvuru) bir değişiklik olarak görüneceği için çağrılacaktır.

Cevabınızda başka bir çözüm buldunuz.

Burada OP ile ilgili bazı yorumları tekrarlamak:

Hâlâ lapsdeğişimi nasıl anlayabileceğimi (kesinlikle ngOnChanges()kendine eşdeğer bir şey kullanıyor olmalı ?) Göremiyorum map.

  • Gelen lapsbileşen kodu / şablon her giriş üzerinde döngüler lapsDatadizisi ve görüntüler içerikleri, bu nedenle görüntülenen her veri parçası üzerinde Açısal bağlamaları vardır.
  • Angular, bir bileşenin girdi özelliklerinde herhangi bir değişiklik algılamadığında ( ===denetimi kullanarak ), yine de (varsayılan olarak) kirli tüm şablon bağlamalarını denetler. Bunlardan herhangi biri değiştiğinde, Angular DOM'u güncelleyecektir. Gördüğün bu.
  • mapsBileşen olasılıkla onun kendi şablonuna herhangi bağlamaları yoktur lapsDatagiriş mülkiyet, değil mi? Bu farkı açıklar.

Not bu lapsDataher iki bileşenin ve rawLapsDataaynı / bir diziye bütün alanına ana bileşeni. Dolayısıyla, Angular lapsDatagirdi özelliklerinde herhangi bir (referans) değişiklik fark etmese de , bileşenler herhangi bir dizi içeriğini "alır" / görürler çünkü hepsi bir diziyi paylaşır / referans verir. İlkel bir türle (dizge, sayı, mantıksal) yapacağımız gibi, bu değişiklikleri yaymak için Angular'a ihtiyacımız yok. Ancak ilkel bir türle, değerdeki herhangi bir değişiklik her zaman tetiklenir ngOnChanges()- ki bu, cevabınızda / çözümünüzde kullandığınız bir şeydir.

Muhtemelen şimdiye kadar anladığınız gibi, nesne girdi özellikleri, dizi girdi özellikleriyle aynı davranışa sahiptir.


Evet, derinlemesine iç içe geçmiş bir nesneyi değiştiriyordum ve sanırım bu, Angular'ın yapıdaki değişiklikleri fark etmesini zorlaştırdı. Mutasyona uğratma tercihim değil ama bu XML'e çevrildi ve sonunda xml'yi yeniden oluşturmak istediğim için çevredeki verilerin hiçbirini kaybetmeyi göze alamam
Simon H

7
@SimonH, "Angular'ın yapıdaki değişiklikleri tespit etmesi zor" - Açıkça söylemek gerekirse, Angular, değişiklikler için diziler veya nesneler olan girdi özelliklerinin içine bile bakmıyor. Yalnızca değerin değişip değişmediğine bakar - nesneler ve diziler için değer referanstır. İlkel türler için değer ... değerdir. (Jargonu doğru bildiğimden emin değilim, ama fikri
anladınız

11
Mükemmel cevap. Angular2 ekibinin çaresizce değişiklik tespitinin dahili unsurları hakkında ayrıntılı, yetkili bir belge yayınlaması gerekiyor.

İşlevi doCheck'te yaparsam, benim durumumda do check birçok kez aranıyor. Lütfen bana başka bir yol söyler misin?
Mr_Perfect

@MarkRajcok bu sorunu
çözmeme

27

En temiz yaklaşım değil, ancak değeri her değiştirdiğinizde nesneyi klonlayabilirsiniz.

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

Sanırım bu yaklaşımı kendi yaklaşımınızı uygulamaya tercih ederim ngDoCheck()ama belki @ GünterZöchbauer gibi biri devreye girebilir.


Hedeflenen bir tarayıcının <Object.assign ()> öğesini destekleyip desteklemeyeceğinden emin değilseniz, ayrıca bir json dizesi oluşturabilir ve json'a geri ayrıştırabilirsiniz, bu da yeni bir nesne oluşturur ...
Guntram

1
@Guntram Veya bir polyfill?
David

12

Case of Arrays'de bunu şu şekilde yapabilirsiniz:

Gelen .tsdosya (Veli bileşeni) nereye güncelliyoruz rawLapsDataböyle yapmak:

rawLapsData = somevalue; // change detection will not happen

Çözüm:

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

ve ngOnChangesalt bileşende çağrılacak


10

Mark Rajcok'un ikinci çözümünün bir uzantısı olarak

Dizi içeriğinde herhangi bir değişiklik yaptığınızda rawLapsData'ya yeni bir dizi atayın. Daha sonra ngOnChanges () çağrılacaktır çünkü dizi (başvuru) bir değişiklik olarak görünecektir

Dizinin içeriğini şu şekilde klonlayabilirsiniz:

rawLapsData = rawLapsData.slice(0);

Bundan bahsediyorum çünkü

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

benim için çalışmadı. Umarım bu yardımcı olur.


8

Veriler harici bir kitaplıktan geliyorsa, içindeki data upate deyimini çalıştırmanız gerekebilir zone.run(...). Bunun için enjekte edin zone: NgZone. Harici kitaplığın somutlaştırılmasını zone.run()zaten çalıştırabiliyorsanız, daha sonra ihtiyacınız olmayabilir zone.run().


OP açıklamalarda belirtildiği gibi, söz konusu değişiklikler json nesnesi içindeki dış ama derin değildi
Simon H

1
Cevabınızın dediği gibi, Angular2'de bir şeyi senkronize tutmak için hala bir şeyler çalıştırmamız gerekiyor, örneğin angular1 öyle $scope.$applymiydi?
Pankaj Parkar

1
Angular dışından bir şey başlatılırsa, bölgeye göre yamalanmış API kullanılmaz ve Angular olası değişiklikler hakkında bilgilendirilmez. Evet, zone.runbenzer $scope.apply.
Günter Zöchbauer

6

ChangeDetectorRef.detectChanges()Angular'a, yuvalanmış bir nesneyi düzenlediğinizde (kirli denetimiyle gözden kaçırdığı) bir değişiklik algılaması çalıştırmasını söylemek için kullanın .


Ama nasıl ? Bunu kullanmaya çalışıyorum, ebeveyn yeni bir öğeyi 2 şekilde sınırlı [(Koleksiyon)] ittikten sonra bir çocukta changeDetection'ı tetiklemek istiyorum.
Deunz

Sorun, değişiklik algılamasının gerçekleşmemesi değil, değişiklik algılamasının değişiklikleri algılamamasıdır! bu yüzden bu bir çözüm gibi görünmüyor! bu yanıt neden beş olumlu oy aldı?
Shahryar Saljoughi

5

Sorununuzu çözmek için 2 çözümüm var

  1. Değiştirilen veya değiştirilmeyen verileri ngDoChecktespit etmek için kullanınobject
  2. Ana bileşenden objectyeni bir bellek adresine atayın object = Object.create(object).

2
Object.create (nesne) ile Object.assign ({}, nesne) arasında kayda değer bir fark var mı?
Daniel Galarza

3

Benim 'hack' çözümüm

   <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 ile aynı anda değişir ve bu, haritaya, değişikliği daha basit bir nesne ilkel türü aracılığıyla algılama şansı verir . Zarif DEĞİL, ama işe yarıyor.


Özellikle orta / büyük ölçekli uygulamalarda, şablon sözdizimindeki çeşitli bileşenlerdeki tüm değişiklikleri izlemekte zorlanıyorum. Genellikle verileri iletmek (hızlı çözüm) için paylaşılan olay yayıcı ve abonelik kullanıyorum veya bunun için Redux modelini (Rx.Subject aracılığıyla) uyguluyorum (planlamak için zaman olduğunda) ...
Sasxa

3

Bir nesnenin bir özelliğini değiştirdiğinizde (iç içe geçmiş nesne dahil) değişiklik algılaması tetiklenmez. Çözümlerden biri, 'lodash' clone () işlevini kullanarak yeni bir nesne referansını yeniden atamak olabilir.

import * as _ from 'lodash';

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

2

İşte beni bununla beladan kurtaran bir hack.

Öyleyse, OP'ye benzer bir senaryo - Kendisine aktarılan veriye ihtiyaç duyan iç içe geçmiş bir Açısal bileşenim var, ancak girdi bir diziyi gösteriyor ve yukarıda belirtildiği gibi, Angular bir değişiklik görmüyor çünkü dizinin içeriği.

Bu yüzden düzeltmek için diziyi Angular'ın bir değişikliği algılaması için bir dizgeye dönüştürüyorum ve ardından iç içe geçmiş bileşende dizeyi tekrar bir diziye ve mutlu günlerine ayırıyorum (',').


1
Yuck! Array.slice (0) yaklaşımı çok daha temizdir.
Chris Haines

2

Ben de aynı ihtiyaca rastladım. Ve ben bununla ilgili çok şey okudum, işte konuyla ilgili makalem.

İtmede değişiklik algılamanızı istiyorsanız, içindeki bir nesnenin değerini değiştirdiğinizde buna sahip olursunuz değil mi? Ve bir şekilde nesneleri kaldırsanız da buna sahip olursunuz.

Daha önce de belirtildiği gibi, changeDetectionStrategy.onPush kullanımı

ChangeDetectionStrategy.onPush ile yaptığınız bu bileşene sahip olduğunuzu varsayalım:

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

Ardından bir öğeyi itip değişiklik algılamasını tetiklersiniz:

myCollection.push(anItem);
refresh();

veya bir öğeyi kaldırıp değişiklik algılamasını tetiklersiniz:

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

veya bir öğe için öznitelik değerini değiştirir ve değişiklik algılamasını tetiklersiniz:

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

Yenileme içeriği:

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

Dilim yöntemi tam olarak aynı Diziyi döndürür ve [=] işareti ona yeni bir referans oluşturarak her ihtiyacınız olduğunda değişiklik algılamasını tetikler. Kolay ve okunabilir :)

Saygılarımızla,


2

Burada belirtilen tüm çözümleri denedim, ancak nedense ngOnChanges()hala benim için ateş etmedim. Bu yüzden bunu denilen this.ngOnChanges()doğru .... benim diziler yeniden doldurur hizmeti çağırdıktan sonra ve işe yaradı? muhtemelen değil. Neat? asla. İşler? Evet!


1

tamam, bu yüzden bunun için çözümüm şuydu:

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

Ve bu beni değişimleri tetikliyor


0

Bunun için bir hack oluşturmam gerekiyordu -

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

Benim durumumda ngOnChange, yakalamadığı nesne değerindeki değişikliklerdi . API çağrısına yanıt olarak birkaç nesne değeri değiştirilir. Nesneyi yeniden başlatmak sorunu çözdü vengOnChange alt bileşende tetiklenmesine .

Gibi bir şey

 this.pagingObj = new Paging(); //This line did the magic
 this.pagingObj.pageNumber = response.PageNumber;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.