wake-up-neo.net

NgFor aktualisiert keine Daten mit Pipe in Angular2

In diesem Szenario zeige ich mit ngFor eine Liste von Schülern (Array) für die Ansicht an:

<li *ngFor="#student of students">{{student.name}}</li>

Es ist wunderbar, dass es immer dann aktualisiert wird, wenn ich einen anderen Schüler zur Liste hinzufüge. 

Wenn ich jedoch eine pipe in filter durch den Schülernamen gebe, 

<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>

Die Liste wird erst dann aktualisiert, wenn ich etwas in das Feld "Filtername des Studenten" eingebe.

Hier ist ein Link zu plnkr .

Hello_world.html

<h1>Students:</h1>
<label for="newStudentName"></label>
<input type="text" name="newStudentName" placeholder="newStudentName" #newStudentElem>
<button (click)="addNewStudent(newStudentElem.value)">Add New Student</button>
<br>
<input type="text" placeholder="Search" #queryElem (keyup)="0">
<ul>
    <li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
</ul>

sort_by_name_pipe.ts

import {Pipe} from 'angular2/core';

@Pipe({
    name: 'sortByName'
})
export class SortByNamePipe {

    transform(value, [queryString]) {
        // console.log(value, queryString);
        return value.filter((student) => new RegExp(queryString).test(student.name))
        // return value;
    }
}

102
Chu Son

Um das Problem und mögliche Lösungen vollständig zu verstehen, müssen wir Angular Änderungserkennung - für Rohre und Komponenten diskutieren.

Rohrwechselerkennung

Staatenlose/reine Pfeifen

Standardmäßig sind Pipes zustandslos/rein. Stateless/Pure Pipes wandeln einfach Eingabedaten in Ausgabedaten um. Sie erinnern sich an nichts, haben also keine Eigenschaften - nur eine transform()-Methode. Angular kann daher die Behandlung von zustandslosen/reinen Pipes optimieren: Wenn sich ihre Eingaben nicht ändern, müssen die Pipes während eines Änderungserkennungszyklus nicht ausgeführt werden. Für eine Pipe wie {{power | exponentialStrength: factor}} sind power und factor Eingaben.

Für diese Frage sind "#student of students | sortByName:queryElem.value", students und queryElem.value Eingaben und pipe sortByName ist zustandslos/rein. students ist ein Array (Referenz).

  • Wenn ein Student hinzugefügt wird, ändert sich die Array-Referenz nicht - students ändert sich nicht - daher wird die zustandslose/reine Pipe nicht ausgeführt.
  • Wenn etwas in die Filtereingabe eingegeben wird, ändert sich queryElem.value, daher wird die zustandslose/reine Pipe ausgeführt.

Eine Möglichkeit, das Array-Problem zu beheben, besteht darin, die Array-Referenz bei jedem Hinzufügen eines Schülers zu ändern - d. H. Bei jedem Hinzufügen eines Schülers ein neues Array zu erstellen. Wir könnten das mit concat() machen:

this.students = this.students.concat([{name: studentName}]);

Obwohl dies funktioniert, sollte unsere Methode addNewStudent() nicht auf eine bestimmte Weise implementiert werden müssen, nur weil wir eine Pipe verwenden. Wir möchten Push() verwenden, um unser Array zu erweitern.

Stateful Pipes

Stateful Pipes haben state - sie haben normalerweise Eigenschaften, nicht nur eine Methode transform(). Sie müssen möglicherweise ausgewertet werden, auch wenn sich ihre Eingaben nicht geändert haben. Wenn wir festlegen, dass eine Pipe statusbehaftet/nicht rein ist - pure: false -, prüft Angulars Änderungserkennungssystem eine Komponente auf Änderungen und diese Komponente verwendet eine statusbehaftete Pipe, um festzustellen, ob sich ihre Eingabe geändert hat oder nicht .

Das klingt nach dem, was wir wollen, obwohl es weniger effizient ist, da die Pipe ausgeführt werden soll, selbst wenn sich die Referenz students nicht geändert hat. Wenn wir die Pipe einfach zustandsbehaftet machen, erhalten wir eine Fehlermeldung:

EXCEPTION: Expression 'students | sortByName:queryElem.value  in [email protected]:6' 
has changed after it was checked. Previous value: '[object Object],[object Object]'. 
Current value: '[object Object],[object Object]' in [students | sortByName:queryElem.value

Laut @drewmoores Antwort "tritt dieser Fehler nur im Dev-Modus auf (der ab Beta-0 standardmäßig aktiviert ist). Wenn Sie beim Booten der App enableProdMode() aufrufen, wird der Fehler nicht ausgelöst . " Das docs for ApplicationRef.tick() lautet:

Im Entwicklungsmodus führt tick () auch einen zweiten Änderungserkennungszyklus durch, um sicherzustellen, dass keine weiteren Änderungen erkannt werden. Wenn während dieses zweiten Zyklus zusätzliche Änderungen erfasst werden, haben Bindungen in der App Nebenwirkungen, die nicht in einem einzigen Änderungserkennungsdurchgang behoben werden können. In diesem Fall gibt Angular einen Fehler aus, da eine Angular -Anwendung nur einen Änderungserkennungsdurchlauf haben kann, in dem alle Änderungserkennungen abgeschlossen sein müssen.

In unserem Szenario glaube ich, dass der Fehler falsch/irreführend ist. Wir haben eine Stateful Pipe und die Ausgabe kann sich jedes Mal ändern, wenn sie aufgerufen wird - sie kann Nebenwirkungen haben und das ist in Ordnung. NgFor wird nach der Pipe ausgewertet, sollte also gut funktionieren.

Wir können jedoch nicht wirklich entwickeln, wenn dieser Fehler ausgelöst wird. Daher besteht eine Problemumgehung darin, der Pipe-Implementierung eine Array-Eigenschaft (d. H. Einen Zustand) hinzuzufügen und dieses Array immer zurückzugeben. Siehe @ Pixelbits Antwort für diese Lösung.

Wir können jedoch effizienter sein, und wir werden sehen, dass wir die Array-Eigenschaft in der Pipe-Implementierung nicht benötigen und keine Problemumgehung für die Erkennung doppelter Änderungen benötigen.

Erkennung von Komponentenwechseln

Standardmäßig durchläuft die Angular Änderungserkennung bei jedem Browserereignis jede Komponente, um festzustellen, ob sie geändert wurde - Eingaben und Vorlagen (und möglicherweise auch andere Dinge?) Werden überprüft.

Wenn wir wissen, dass eine Komponente nur von ihren Eingabeeigenschaften (und Vorlagenereignissen) abhängt und dass die Eingabeeigenschaften unveränderlich sind, können wir die effizientere Änderungserkennungsstrategie onPush verwenden. Bei dieser Strategie wird eine Komponente nicht bei jedem Browserereignis überprüft, sondern nur, wenn sich die Eingaben ändern und wenn Vorlagenereignisse ausgelöst werden. Und anscheinend wird bei dieser Einstellung dieser Expression ... has changed after it was checked-Fehler nicht angezeigt. Dies liegt daran, dass eine onPush-Komponente erst dann erneut überprüft wird, wenn sie erneut "markiert" ist (ChangeDetectorRef.markForCheck()). So werden Template-Bindings und Stateful-Pipe-Ausgaben nur einmal ausgeführt/ausgewertet. Stateless/Pure-Pipes werden erst ausgeführt, wenn sich ihre Eingaben ändern. Wir brauchen hier also noch eine zustandsbehaftete Pfeife.

Dies ist die von @EricMartinez vorgeschlagene Lösung: Stateful Pipe mit onPush Änderungserkennung. Siehe @ caffinatedmonkeys Antwort für diese Lösung.

Beachten Sie, dass bei dieser Lösung die Methode transform() nicht jedes Mal dasselbe Array zurückgeben muss. Das finde ich allerdings etwas seltsam: eine zustandsbehaftete Pfeife ohne Zustand. Denken Sie noch einmal darüber nach ... die Stateful-Pipe sollte wahrscheinlich immer das gleiche Array zurückgeben. Andernfalls kann es nur mit onPush Komponenten im dev-Modus verwendet werden.


Nach all dem denke ich, dass mir eine Kombination aus den Antworten von @ Eric und @ pixelbits gefällt: Stateful Pipe, die dieselbe Array-Referenz zurückgibt, mit onPush Änderungserkennung, wenn die Komponente dies zulässt. Da die Stateful-Pipe dieselbe Array-Referenz zurückgibt, kann die Pipe weiterhin mit Komponenten verwendet werden, die nicht mit onPush konfiguriert sind.

Plunker

Dies wird wahrscheinlich zu einem Angular 2-Idiom: Wenn ein Array eine Pipe speist und sich das Array möglicherweise ändert (die Elemente im Array, die nicht die Arrayreferenz sind), müssen wir eine stateful-Pipe verwenden .

137
Mark Rajcok

Wie Eric Martinez in den Kommentaren darauf hingewiesen hat, wird das Problem durch das Hinzufügen von pure: false zu Ihrem Pipe-Dekorateur und changeDetection: ChangeDetectionStrategy.OnPush zu Ihrem Component-Dekorateur behoben. Hier ist ein Arbeitsplan. Das Ändern zu ChangeDetectionStrategy.Always funktioniert ebenfalls. Hier ist der Grund.

Gemäß der angle2-Anleitung für Rohre :

Pipes sind standardmäßig zustandslos. Wir müssen eine Pipe als stateful deklarieren, indem Sie die pure-Eigenschaft des @Pipe-Dekorators auf false setzen. Diese Einstellung teilt Angulars Änderungserkennungssystem mit, die Ausgabe dieser Pipe bei jedem Zyklus zu überprüfen, ob sich ihre Eingabe geändert hat oder nicht.

Wie bei der ChangeDetectionStrategy werden standardmäßig alle Bindungen in jedem Zyklus geprüft. Wenn eine pure: false-Pipe hinzugefügt wird, ändert sich die Änderungserkennungsmethode aus Performancegründen von CheckAlways in CheckOnce. Mit OnPush werden Bindungen für die Komponente nur geprüft, wenn sich eine Eingabeeigenschaft ändert oder wenn ein Ereignis ausgelöst wird. Weitere Informationen zu Änderungsdetektoren, einem wichtigen Teil von angular2, finden Sie unter den folgenden Links:

25
0xcaff

Demo Plunkr

Sie müssen ChangeDetectionStrategy nicht ändern. Die Implementierung einer Stateful Pipe reicht aus, um alles zum Laufen zu bringen.

Dies ist eine Stateful-Pipe (es wurden keine weiteren Änderungen vorgenommen):

@Pipe({
  name: 'sortByName',
  pure: false
})
export class SortByNamePipe {
  tmp = [];
  transform (value, [queryString]) {
    this.tmp.length = 0;
    // console.log(value, queryString);
    var arr = value.filter((student)=>new RegExp(queryString).test(student.name));
    for (var i =0; i < arr.length; ++i) {
        this.tmp.Push(arr[i]);
     }

    return this.tmp;
  }
}
19
pixelbits

Aus der Winkeldokumentation

Reine und unreine Pfeifen

Es gibt zwei Kategorien von Pfeifen: rein und unrein. Pipes sind standardmäßig rein. Jede Pfeife, die Sie bisher gesehen haben, war rein. Sie machen eine Pipe unrein, indem Sie deren pure Flag auf false setzen. Sie könnten das FlyingHeroesPipe so unrein machen:

@Pipe({ name: 'flyingHeroesImpure', pure: false })

Bevor Sie das tun, sollten Sie den Unterschied zwischen rein und unrein verstehen, beginnend mit einer reinen Pfeife.

Pure Pipes Angular führt ein Pure Pipes nur aus, wenn eine reine Änderung des Eingabewerts erkannt wird. Eine reine Änderung ist entweder eine Änderung an einem primären Eingabewert (String, Number, Boolean, Symbol) oder eine geänderte Objektreferenz (Datum, Array, Funktion, Objekt).

Winkel ignoriert Änderungen in (zusammengesetzten) Objekten. Es wird keine reine Pipe aufgerufen, wenn Sie einen Eingabemonat ändern, einem Eingabearray hinzufügen oder eine Eingabeobjekteigenschaft aktualisieren.

Das mag einschränkend wirken, ist aber auch schnell. Eine Objektreferenzprüfung ist schnell - viel schneller als eine eingehende Prüfung auf Unterschiede -, so dass Angular schnell feststellen kann, ob sowohl die Pipe-Ausführung als auch eine Ansichtsaktualisierung übersprungen werden kann.

Aus diesem Grund ist eine reine Pipe vorzuziehen, wenn Sie mit der Änderungserkennungsstrategie leben können. Wenn Sie nicht können, können Sie die unreine Leitung verwenden.

8
Sumit Jaiswal

Fügen Sie dem Pipe-Extra-Parameter hinzu, und ändern Sie ihn direkt nach der Array-Änderung. Selbst bei einer Pipe mit reinem Pipe wird die Liste aktualisiert

artikel zulassen | Rohr: Param

0
Nick Bistrov

In diesem Anwendungsfall habe ich meine Pipe in ts-Datei für die Datenfilterung verwendet. Es ist viel leistungsfähiger als die Verwendung von reinen Rohren. Verwenden Sie in ts wie folgt:

import { YourPipeComponentName } from 'YourPipeComponentPath';

class YourService {

  constructor(private pipe: YourPipeComponentName) {}

  YourFunction(value) {
    this.pipe.transform(value, 'pipeFilter');
  }
}
0
Andris

Problemumgehung: Importieren Sie Pipe manuell im Konstruktor und rufen Sie die Transformationsmethode mit dieser Pipe auf

constructor(
private searchFilter : TableFilterPipe) { }

onChange() {
   this.data = this.searchFilter.transform(this.sourceData, this.searchText)}

Eigentlich brauchst du nicht mal eine Pfeife

0
Richard Luo

Anstelle von pure: false. Sie können den Wert in der Komponente tief kopieren und durch this.students = Object.assign ([], NEW_ARRAY) ersetzen. wobei NEW_ARRAY das modifizierte Array ist.

Es funktioniert für Winkel 6 und sollte auch für andere Winkelversionen funktionieren.

0
ideeps