it-swarm.com.de

Angular2 Änderungserkennung: ngOnChanges wird für verschachteltes Objekt nicht ausgelöst

Ich weiß, dass ich nicht der erste bin, der danach fragt, aber ich habe in den vorherigen Fragen keine Antwort gefunden. Ich habe das in einer Komponente

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

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

Im Controller wird rawLapsdata von Zeit zu Zeit mutiert.

In laps werden die Daten als HTML in einem Tabellenformat ausgegeben. Dies ändert sich, wenn sich rawLapsdata ändert.

Meine map-Komponente muss ngOnChanges als Auslöser verwenden, um Markierungen in einer Google Map neu zu zeichnen. Das Problem ist, dass ngOnChanges nicht ausgelöst wird, wenn sich rawLapsData im übergeordneten Element ändert. Was kann ich machen?

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 funktioniert nicht, aber es sieht so aus, als würde lapsData aktualisiert. In ngInit befindet sich ein Ereignis-Listener für Zoomänderungen, der auch this.drawmarkers aufruft. Wenn ich den Zoom ändere, sehe ich tatsächlich eine Änderung in den Markierungen. Das einzige Problem ist also, dass ich die Benachrichtigung nicht bekomme, wenn sich die Eingabedaten ändern.

In der Muttergesellschaft habe ich diese Zeile. (Denken Sie daran, dass sich die Änderung in Runden widerspiegelt, nicht jedoch in der Karte.).

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

Beachten Sie, dass this.rawLapsData selbst ein Zeiger auf die Mitte eines großen Json-Objekts ist

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

rawLapsData zeigt weiterhin auf dasselbe Array, auch wenn Sie den Inhalt des Arrays ändern (z. B. Elemente hinzufügen, Elemente entfernen, Elemente ändern). 

Wenn Angular während der Änderungserkennung die Eingabeeigenschaften von Komponenten auf Änderungen überprüft, verwendet es (im Wesentlichen) === für die fehlerhafte Überprüfung. Für Arrays bedeutet dies, dass (nur) die Arrayreferenzen nicht geprüft werden. Da sich die rawLapsData-Arrayreferenz nicht ändert, wird ngOnChanges() nicht aufgerufen. 

Ich kann mir zwei mögliche Lösungen vorstellen:

  1. Implementieren Sie ngDoCheck() und führen Sie Ihre eigene Änderungserkennungslogik aus, um festzustellen, ob sich der Inhalt des Arrays geändert hat. (Das Dokument Lifecycle Hooks hat ein Beispiel .)

  2. Weisen Sie rawLapsData ein neues Array zu, wenn Sie Änderungen am Array-Inhalt vornehmen. Dann wird ngOnChanges() aufgerufen, da das Array (Referenz) als Änderung angezeigt wird.

In Ihrer Antwort haben Sie eine andere Lösung gefunden.

Einige Bemerkungen hier zum OP wiederholen:

Ich sehe immer noch nicht, wie laps die Änderung auffangen kann (sicherlich muss etwas Äquivalent zu ngOnChanges() selbst verwendet werden?), Während map dies nicht kann.

  • In der laps-Komponente durchläuft Ihr Code/Template die einzelnen Einträge im lapsData-Array und zeigt den Inhalt an, sodass für jedes angezeigte Datenelement Angular-Bindungen vorhanden sind.
  • Selbst wenn Angular keine Änderungen an den Eingabeeigenschaften einer Komponente feststellt (mithilfe der ===-Prüfung), prüft Dirty (standardmäßig) alle Vorlagenbindungen. Wenn sich eine dieser Änderungen ändert, aktualisiert Angular das DOM. Das sehen Sie.
  • Die maps-Komponente hat in ihrer Vorlage wahrscheinlich keine Bindungen an ihre Input-Eigenschaft lapsData, oder? Das würde den Unterschied erklären.

Beachten Sie, dass lapsData in beiden Komponenten und rawLapsData in der übergeordneten Komponente alle auf dasselbe Array zeigen. Auch wenn Angular keine (Referenz-) Änderungen an den Eingabeeigenschaften von lapsData feststellt, "sehen"/sehen die Komponenten Änderungen des Array-Inhalts, da sie alle dieses Array gemeinsam nutzen. Wir brauchen Angular nicht, um diese Änderungen zu verbreiten, wie wir es mit einem primitiven Typ tun würden (string, number, boolean). Bei einem primitiven Typ würde jedoch jede Änderung des Werts ngOnChanges() auslösen. Dies ist etwas, das Sie in Ihrer Antwort/Lösung ausnutzen.

Wie Sie wahrscheinlich bereits herausgefunden haben, haben Objekteingabeeigenschaften dasselbe Verhalten wie die Eingabeeigenschaften von Arrays.

97
Mark Rajcok

Nicht der sauberste Ansatz, aber Sie können das Objekt jedes Mal einfach klonen, wenn Sie den Wert ändern.

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

Ich denke, ich würde diesen Ansatz der Implementierung einer eigenen ngDoCheck() vorziehen, aber vielleicht könnte jemand wie @ GünterZöchbauer klingeln.

14
David

Als Erweiterung zu Mark Rajcoks zweiter Lösung

Weisen Sie rawLapsData ein neues Array zu, wenn Sie Änderungen an der .__ vornehmen. Inhalt des Arrays Dann wird ngOnChanges () wegen des Arrays .__ aufgerufen. (Referenz) wird als Änderung angezeigt

sie können den Inhalt des Arrays folgendermaßen klonen:

rawLapsData = rawLapsData.slice(0);

Ich erwähne das, weil 

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

funktionierte nicht für mich Ich hoffe das hilft.

8
decebal

Wenn die Daten aus einer externen Bibliothek stammen, müssen Sie möglicherweise die Anweisung data upate in zone.run(...) ausführen. Injizieren Sie zone: NgZone dafür. Wenn Sie die Instantiierung der externen Bibliothek bereits in zone.run() ausführen können, benötigen Sie zone.run() später möglicherweise nicht.

6

Verwenden Sie ChangeDetectorRef.detectChanges(), um Angular mitzuteilen, dass eine Änderungserkennung ausgeführt wird, wenn Sie ein verschachteltes Objekt bearbeiten (das bei der fehlerhaften Überprüfung nicht vorhanden ist).

6
Tim Phillips

Meine "Hack" -Lösung ist

   <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 ändert sich gleichzeitig mit rawLapsData und dies gibt map eine weitere Chance, die Änderung durch ein einfacheres Verfahren zu erkennen objekt primitiver Typ. Es ist NICHT elegant, aber es funktioniert.

3
Simon H

Bei Arrays können Sie dies folgendermaßen tun:

In .ts-Datei (übergeordnete Komponente), in der Sie Ihre rawLapsData aktualisieren, gehen Sie folgendermaßen vor

rawLapsData = somevalue; // change detection will not happen

Lösung: 

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

und ngOnChanges wird in der untergeordneten Komponente aufgerufen

3
Danish Dullu

Hier ist ein Hack, der mich gerade aus diesem Problem gebracht hat.

Ein ähnliches Szenario wie beim OP. Ich habe eine verschachtelte Angular-Komponente, für die Daten weitergegeben werden müssen, aber die Eingabe zeigt auf ein Array, und wie oben erwähnt, sieht Angular keine Änderung, da es das nicht untersucht Inhalt des Arrays.

Um dies zu beheben, konvertiere ich das Array in eine Zeichenfolge, damit Angular eine Änderung erkennt, und in der verschachtelten Komponente habe ich die Zeichenfolge (',') wieder in ein Array und die glücklichen Tage aufgeteilt.

2
Graham Phillips

ok also meine Lösung dafür war:

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

Und das löst mich aus

0
DanielWaw

Die Änderungserkennung wird nicht ausgelöst, wenn Sie eine Eigenschaft eines Objekts (einschließlich verschachteltes Objekt) ändern. Eine Lösung wäre, eine neue Objektreferenz mithilfe der 'lodash'-Funktion clone () neu zuzuweisen.

import * as _ from 'lodash';

this.foo = _.clone(this.foo);
0
Anton Nikprelaj

Ich habe 2 Lösungen, um Ihr Problem zu lösen 

  1. Verwenden Sie ngDoCheck, um object-Daten zu ermitteln, die geändert wurden oder nicht 
  2. Weisen Sie object von object = Object.create(object) aus der übergeordneten Komponente eine neue Speicheradresse zu.
0
bountyhunter

Ich habe alle hier genannten Lösungen ausprobiert, aber aus irgendeinem Grund hat ngOnChanges() immer noch nicht für mich ausgelöst. Also rief ich es mit this.ngOnChanges() an, nachdem ich den Dienst angerufen hatte, der meine Arrays auffüllt und es funktionierte .... richtig? wahrscheinlich nicht. Ordentlich? Auf keinen Fall. Funktioniert? Ja!

0
Yazan Khalaileh

Ich bin über das gleiche Bedürfnis gestolpert. Und ich habe viel darüber gelesen, also hier mein Kupfer zum Thema.

Wenn Sie Ihre Änderungserkennung auf Push setzen möchten, hätten Sie sie, wenn Sie den Wert eines Objekts rechts ändern. Und Sie hätten es auch, wenn Sie Objekte entfernen.

Wie bereits erwähnt, changeDetectionStrategy.onPush verwenden

Angenommen, Sie haben diese Komponente mit changeDetectionStrategy.onPush erstellt:

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

Dann drücken Sie ein Element und lösen die Änderungserkennung aus: 

myCollection.Push(anItem);
refresh();

oder Sie würden ein Element entfernen und die Änderungserkennung auslösen: 

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

oder Sie würden einen Attributwert für ein Element ändern und die Änderungserkennung auslösen: 

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

Inhalt der Aktualisierung: 

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

Die Slice-Methode gibt genau dasselbe Array zurück, und das [=] - Zeichen stellt eine neue Referenz dar, die die Änderungserkennung jedes Mal auslöst, wenn Sie es benötigen. Einfach und lesbar :)

Grüße,

0
Deunz