Winkel 2: Wie erkennt man Änderungen in einem Array? (@input Eigenschaft)

74

Ich habe eine übergeordnete Komponente, die ein Array von Objekten mithilfe einer Ajax-Anforderung abruft.

Diese Komponente hat zwei untergeordnete Komponenten: Eine zeigt die Objekte in einer Baumstruktur und die andere rendert ihren Inhalt in einem Tabellenformat. Das übergeordnete Element übergibt das Array über eine @ input-Eigenschaft an seine untergeordneten Elemente und zeigt den Inhalt ordnungsgemäß an. Alles wie erwartet.

Das Problem tritt auf, wenn Sie ein Feld innerhalb der Objekte ändern: Die untergeordneten Komponenten werden nicht über diese Änderungen benachrichtigt. Änderungen werden nur ausgelöst, wenn Sie das Array manuell seiner Variablen zuweisen.

Ich bin es gewohnt, mit Knockout JS zu arbeiten, und ich muss einen ähnlichen Effekt erzielen wie ObservableArrays.

Ich habe etwas über DoCheck gelesen, bin mir aber nicht sicher, wie es funktioniert.

pablolmedorado
quelle
7
@ Adam nicht so flach sein
Renaud

Antworten:

105

OnChanges Der Lifecycle Hook wird nur ausgelöst, wenn sich die Instanz der Eingabeeigenschaft ändert.

Wenn Sie überprüfen möchten, ob ein Element im Eingabearray hinzugefügt, verschoben oder entfernt wurde, können Sie IterableDiffers im DoCheckLifecycle Hook wie folgt verwenden:

constructor(private iterableDiffers: IterableDiffers) {
    this.iterableDiffer = iterableDiffers.find([]).create(null);
}

ngDoCheck() {
    let changes = this.iterableDiffer.diff(this.inputArray);
    if (changes) {
        console.log('Changes detected!');
    }
}

Wenn Sie Änderungen an Objekten in einem Array erkennen müssen, müssen Sie alle Elemente durchlaufen und KeyValueDiffers für jedes Element anwenden . (Sie können dies parallel zur vorherigen Prüfung tun).

Weitere Informationen finden Sie in diesem Beitrag: Erkennen Sie Änderungen an Objekten innerhalb des Arrays in Angular2

seidme
quelle
1
Die diffMethode wurde umbenannt in find: angular.io/api/core/IterableDiffers
Sparafusile
4
@Sparafusile Ihr Link zeigt auf IterableDiffers, nicht auf die IterableDifferSchnittstelle. Bitte überprüfen Sie: angle.io/api/core/IterableDiffer
seidme
3
Hoppla. Ich habe gerade einen Termin mit meinem Optiker für eine neue Brille vereinbart.
Sparafusile
4
Denken Sie daran, den iterableDiffer wie folgt zu initialisiereniterableDiffer:IterableDiffer<YourSingleItemType>;
Julian Torregrosa
3
ngDoCheckist teuer für Ressourcen, da ich es mit der Suchmethode verwendet habe, aber die Art und Weise, wie @SeidMehmedovic es tat, ist die beste für die Leistung. Daumen hoch
WasiF
21

Sie können jederzeit einen neuen Verweis auf das Array erstellen, indem Sie ihn mit einem leeren Array zusammenführen:

this.yourArray = [{...}, {...}, {...}];
this.yourArray[0].yourModifiedField = "whatever";

this.yourArray = [].concat(this.yourArray);

Der obige Code ändert die Array-Referenz und löst den OnChanges-Mechanismus in untergeordneten Komponenten aus.

Wojtek Majerski
quelle
Das hat in meiner Situation ziemlich gut funktioniert. <dhi-context-menu [selectedJobs] = "[]. concat (selectedJobs $ | async)"> </ dhi-context-menu>
Jared Christensen
Wie ist das aus Sicht der Leistung und des Speicherverbrauchs?
Ernesto Alfonso
@ErnestoAlfonso: Es handelt sich um eine Operation ohne Leistung, da Sie den Array-Inhalt (Elemente) nicht klonen. Sie erstellen nur eine neue Variable, die auf denselben Speicherbereich wie die vorherige verweist.
Wojtek Majerski
Ihre Antwort hat mir den Tag gerettet
Stringnome
11

Lesen Sie den folgenden Artikel und verpassen Sie keine veränderlichen oder unveränderlichen Objekte.

Das Hauptproblem besteht darin, dass Sie Array-Elemente mutieren, während die Array-Referenz gleich bleibt. Die Angular2-Änderungserkennung überprüft nur die Array-Referenz, um Änderungen zu erkennen. Nachdem Sie das Konzept unveränderlicher Objekte verstanden haben, werden Sie verstehen, warum Sie ein Problem haben und wie Sie es lösen können.

Ich verwende den Redux Store in einem meiner Projekte, um solche Probleme zu vermeiden.

https://blog.ehowtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

Jan Cejka
quelle
8

Sie können IterableDiffers verwenden

Es wird von * ngFor verwendet

constructor(private _differs: IterableDiffers) {}

ngOnChanges(changes: SimpleChanges): void {
  if (!this._differ && value) {
     this._differ = this._differs.find(value).create(this.ngForTrackBy);
  }
}

ngDoCheck(): void {
  if (this._differ) {
    const changes = this._differ.diff(this.ngForOf);
    if (changes) this._applyChanges(changes);
  }
}
Günter Zöchbauer
quelle
3

Dies erscheint bereits beantwortet. Für zukünftige Problem-Suchende wollte ich jedoch etwas hinzufügen, das bei der Untersuchung und Fehlerbehebung eines Problems zur Änderungserkennung, das ich hatte, übersehen wurde. Jetzt war mein Problem ein wenig isoliert und zugegebenermaßen ein dummer Fehler für mich, aber dennoch relevant. Stellen Sie beim Aktualisieren der Werte in Arrayoder Objectin der Referenz sicher, dass Sie sich im richtigen Bereich befinden. Ich habe mich in eine Falle gestellt, indem ich verwendet habe setInterval(myService.function, 1000), myService.function()um die Werte eines öffentlichen Arrays zu aktualisieren, das ich außerhalb des Dienstes verwendet habe. Dadurch wurde das Array nie aktualisiert, da die Bindung deaktiviert war und die korrekte Verwendung hätte erfolgen müssen setInterval(myService.function.bind(this), 1000). Ich habe meine Zeit damit verschwendet, Änderungserkennungs-Hacks zu versuchen, als es ein dummer / einfacher Fehler war. Beseitigen Sie den Umfang als Schuldigen, bevor Sie Lösungen zur Änderungserkennung ausprobieren. es könnte Ihnen einige Zeit sparen.

bess
quelle
Sie hatten mich bei "Versetzen Sie sich mit setInterval (myService.function, 1000) in eine Falle"
Sebastian Wozny
1

Sie können ein unreines Rohr verwenden, wenn Sie das Array in Ihrer Komponentenvorlage direkt verwenden. (Dieses Beispiel ist für einfache Arrays gedacht, für die keine eingehende Prüfung erforderlich ist.)

@Pipe({
  name: 'arrayChangeDetector',
  pure: false
})
export class ArrayChangeDetectorPipe implements PipeTransform {
  private differ: IterableDiffer<any>;

  constructor(iDiff: IterableDiffers) {
    this.differ = iDiff.find([]).create();
  }

  transform(value: any[]): any[] {
    if (this.differ.diff(value)) {
      return [...value];
    }
    return value;
  }
}
<cmp [items]="arrayInput | arrayChangeDetector"></cmp>

Für diejenigen Zeitreisenden unter uns, die immer noch auf Array-Probleme stoßen, gibt es hier eine Reproduktion des Problems zusammen mit mehreren möglichen Lösungen.

https://stackblitz.com/edit/array-value-changes-not-detected-ang-8

Lösungen umfassen:

  • NgDoCheck
  • Mit einem Rohr
  • Verwenden von unveränderlichem JS NPM- Github
Reims
quelle
1

Es ist Arbeit für mich:

@Component({
  selector: 'my-component',
  templateUrl: './my-component.component.html',
  styleUrls: ['./my-component.component.scss']
})
export class MyComponent implements DoCheck {

  @Input() changeArray: MyClassArray[]= [];
  private differ: IterableDiffers;

  constructor(private differs: IterableDiffers) {
    this.differ = differs;
  }

  ngDoCheck() {
    const changes = this.differ.find(this.insertedTasks);
    if (changes) {
      this.myMethodAfterChange();
  }
}
Stapel über
quelle
Ich markiere dich dafür, es hat mir mehr geholfen als allen anderen. Nicht sicher, warum Sie mit einem Minus markiert wurden!
r3plica
2
Meines Erachtens ist dies keine Lösung, wie sie changesimmer festgelegt ist - was bedeutet, dass Sie sie einfach im this.myMethodAfterChange();Inneren tun können ngDoCheck. Dies würde funktionieren, aber ein Leistungsproblem verursachen.
NoRyb