In diesem Szenario zeige ich der Ansicht eine Liste der Schüler (Array) mit folgenden Eigenschaften an ngFor
:
<li *ngFor="#student of students">{{student.name}}</li>
Es ist wunderbar, dass es aktualisiert wird, wenn ich einen anderen Schüler zur Liste hinzufüge.
Wenn ich es pipe
jedoch filter
mit dem Namen des Schülers gebe ,
<li *ngFor="#student of students | sortByName:queryElem.value ">{{student.name}}</li>
Die Liste wird erst aktualisiert, wenn ich etwas in das Feld zum Filtern des Schülernamens 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;
}
}
pure:false
in Ihrem Rohr undchangeDetection: ChangeDetectionStrategy.OnPush
in Ihrer Komponente hinzu..test()
in Ihrer Filterfunktion zu verwenden. Dies liegt daran, dass Ihr Code unterbrochen wird , wenn der Benutzer eine Zeichenfolge eingibt, die Sonderzeichen wie:*
oder+
usw. enthält. Ich denke, Sie sollten.includes()
Abfragezeichenfolge mit benutzerdefinierten Funktion verwenden oder entkommen.pure:false
und Bereitstellen Ihrer Pipe wird das Problem behoben. Das Ändern von ChangeDetectionStrategy ist nicht erforderlich.Antworten:
Um das Problem und mögliche Lösungen vollständig zu verstehen, müssen wir die Erkennung von Winkeländerungen diskutieren - für Rohre und Komponenten.
Rohrwechselerkennung
Staatenlose / reine Rohre
Standardmäßig sind Pipes zustandslos / rein. Zustandslose / reine 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 Rohren optimieren: Wenn sich ihre Eingänge nicht ändern, müssen die Rohre während eines Änderungserkennungszyklus nicht ausgeführt werden. Für ein Rohr , wie beispielsweise{{power | exponentialStrength: factor}}
,power
undfactor
sind Eingänge.Für diese Frage
"#student of students | sortByName:queryElem.value"
,students
undqueryElem.value
sind Eingänge, und das RohrsortByName
ist zustandslos / rein.students
ist ein Array (Referenz).students
ändert sich nicht - daher wird die zustandslose / reine Pipe nicht ausgeführt.queryElem.value
ändert sich dies, sodass die zustandslose / reine Pipe ausgeführt wird.Eine Möglichkeit, das Array-Problem zu beheben, besteht darin, die Array-Referenz jedes Mal zu ändern, wenn ein Schüler hinzugefügt wird, dh jedes Mal, wenn ein Schüler hinzugefügt wird, ein neues Array zu erstellen. Wir könnten dies tun mit
concat()
:Obwohl dies funktioniert, sollte unsere
addNewStudent()
Methode nicht auf eine bestimmte Weise implementiert werden müssen, nur weil wir eine Pipe verwenden. Wir möchten verwendenpush()
, um unser Array zu erweitern.Stateful Pipes
Stateful Pipes haben State - sie haben normalerweise Eigenschaften, nicht nur eine
transform()
Methode. Sie müssen möglicherweise ausgewertet werden, auch wenn sich ihre Eingaben nicht geändert haben. Wenn wir angeben, dass eine Pipe zustandsbehaftet / nicht rein ist -pure: false
-, prüft das Änderungserkennungssystem von Angular, wenn eine Komponente auf Änderungen überprüft und diese Komponente eine zustandsbehaftete Pipe verwendet, die Ausgabe der Pipe, ob sich ihre Eingabe geändert hat oder nicht.Dies klingt nach dem, was wir wollen, obwohl es weniger effizient ist, da die Pipe auch dann ausgeführt werden soll, wenn sich die
students
Referenz nicht geändert hat. Wenn wir die Pipe einfach zustandsbehaftet machen, erhalten wir eine Fehlermeldung:Laut der Antwort von @ drawmoore "tritt dieser Fehler nur im Entwicklungsmodus auf (der ab Beta-0 standardmäßig aktiviert ist). Wenn Sie
enableProdMode()
beim Booten der App aufrufen , wird der Fehler nicht ausgelöst." Die Dokumente für denApplicationRef.tick()
Status:In unserem Szenario glaube ich, dass der Fehler falsch / irreführend ist. Wir haben eine Stateful-Pipe, und die Ausgabe kann sich bei jedem Aufruf ändern - sie kann Nebenwirkungen haben, und das ist in Ordnung. NgFor wird nach dem Rohr ausgewertet, daher sollte es gut funktionieren.
Wir können uns jedoch nicht wirklich entwickeln, wenn dieser Fehler ausgelöst wird. Eine Problemumgehung besteht darin, der Pipe-Implementierung eine Array-Eigenschaft (dh einen Status) hinzuzufügen und dieses Array immer zurückzugeben. Siehe die Antwort von @ pixelbits für diese Lösung.
Wir können jedoch effizienter sein, und wie wir sehen werden, benötigen wir die Array-Eigenschaft in der Pipe-Implementierung nicht und wir benötigen keine Problemumgehung für die Doppeländerungserkennung.
Erkennung von Komponentenänderungen
Standardmäßig durchläuft die Erkennung von Winkeländerungen bei jedem Browserereignis jede Komponente, um festzustellen, ob sie sich geändert hat. Eingaben und Vorlagen (und möglicherweise 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 wesentlich effizientere
onPush
Strategie zur Änderungserkennung verwenden. Bei dieser Strategie wird anstelle jedes Browserereignisses eine Komponente nur dann überprüft, wenn sich die Eingaben ändern und wenn Vorlagenereignisse ausgelöst werden. Und anscheinend bekommen wir diesenExpression ... has changed after it was checked
Fehler mit dieser Einstellung nicht. Dies liegt daran, dass eineonPush
Komponente erst dann erneut überprüft wird, wenn sie erneut "markiert" (ChangeDetectorRef.markForCheck()
) ist. Template-Bindungen und Stateful-Pipe-Ausgaben werden also nur einmal ausgeführt / ausgewertet. Zustandslose / reine Pipes werden immer noch nicht ausgeführt, es sei denn, ihre Eingaben ändern sich. Also brauchen wir hier noch eine Stateful Pipe.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
transform()
Methode nicht jedes Mal dasselbe Array zurückgeben muss. Ich finde das allerdings etwas seltsam: eine zustandsbehaftete Pipe ohne Zustand. Denken Sie noch etwas darüber nach ... die Stateful Pipe sollte wahrscheinlich immer das gleiche Array zurückgeben. Andernfalls könnte es nur mitonPush
Komponenten im Dev-Modus verwendet werden.Nach all dem mag ich eine Kombination aus den Antworten von @ Eric und @ pixelbits: 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 konfiguriert sindonPush
.Plunker
Dies wird wahrscheinlich zu einer Angular 2-Redewendung: Wenn ein Array eine Pipe speist und sich das Array möglicherweise ändert (die Elemente im Array, nicht die Array-Referenz), müssen wir eine Stateful-Pipe verwenden.
quelle
onPush
Änderungserkennung plnkr.co/edit/gRl0Pt9oBO6038kCXZPk?p=previewWie Eric Martinez in den Kommentaren betonte, wird Ihr Problem durch Hinzufügen
pure: false
zu IhremPipe
Dekorateur undchangeDetection: ChangeDetectionStrategy.OnPush
IhremComponent
Dekorateur behoben. Hier ist ein funktionierender Plunkr. Wechseln zuChangeDetectionStrategy.Always
, funktioniert auch. Hier ist der Grund.Entsprechend der Winkelführung2 an Rohren :
Wie für die in
ChangeDetectionStrategy
der Standardeinstellung werden alle Bindungen jeden einzelnen Zyklus überprüft. Wenn einepure: false
Pipe hinzugefügt wird, ändert sich meines Erachtens die Änderungserkennungsmethode aus Leistungsgründen von vonCheckAlways
nachCheckOnce
. MitOnPush
werden Bindungen für die Komponente nur überprüft, wenn sich eine Eingabeeigenschaft ändert oder wenn ein Ereignis ausgelöst wird. Weitere Informationen zu Änderungsdetektoren, ein wichtiger Bestandteil vonangular2
, finden Sie unter den folgenden Links:quelle
pure: false
Pipe hinzugefügt wird, ändert sich meiner Meinung nach die Änderungserkennungsmethode von CheckAlways zu CheckOnce" - das stimmt nicht mit dem überein, was Sie aus den Dokumenten zitiert haben. Mein Verständnis ist wie folgt: Die Eingaben einer zustandslosen Pipe werden standardmäßig in jedem Zyklus überprüft. Bei einer Änderung wird die Pipe-transform()
Methode aufgerufen, um die Ausgabe (neu) zu generieren. Dietransform()
Methode einer Stateful Pipe wird in jedem Zyklus aufgerufen und ihre Ausgabe auf Änderungen überprüft. Siehe auch stackoverflow.com/a/34477111/215945 .OnPush
(dh die Komponente wird als "unveränderlich" markiert), die Stateful-Pipe-Ausgabe nicht "stabilisiert" werden muss - dh dietransform()
Methode wird anscheinend nur einmal ausgeführt (wahrscheinlich alle) Die Änderungserkennung für die Komponente wird nur einmal ausgeführt. Vergleichen Sie dies mit der Antwort von @ pixelbits, bei der dietransform()
Methode mehrmals aufgerufen wird und sich stabilisieren muss (gemäß der anderen Antwort von pixelbits ), weshalb dieselbe Array-Referenz verwendet werden muss.transform
es nur aufgerufen wird, wenn neue Daten anstatt mehrmals gepusht werden, bis sich die Ausgabe stabilisiert, oder?Demo Plunkr
Sie müssen die ChangeDetectionStrategy nicht ändern. Die Implementierung eines Stateful Pipe reicht aus, um alles zum Laufen zu bringen.
Dies ist eine Stateful Pipe (es wurden keine weiteren Änderungen vorgenommen):
quelle
Expression 'students | sortByName:queryElem.value in HelloWorld@7:6' has changed after it was checked. Previous value: '[object Object],[object Object],[object Object]'. Current value: '[object Object],[object Object],[object Object]' in [students | sortByName:queryElem.value in HelloWorld@7:6]
Aus der eckigen Dokumentation
Reine und unreine Rohre
Es gibt zwei Kategorien von Rohren: rein und unrein. Rohre sind standardmäßig rein. Jede Pfeife, die Sie bisher gesehen haben, war rein. Sie machen ein Rohr unrein, indem Sie sein reines Flag auf false setzen. Sie könnten die FlyingHeroesPipe so unrein machen:
@Pipe({ name: 'flyingHeroesImpure', pure: false })
Bevor Sie dies tun, sollten Sie den Unterschied zwischen rein und unrein verstehen, beginnend mit einem reinen Rohr.
Reine Rohre Angular führt ein reines Rohr nur aus, wenn eine reine Änderung des Eingabewerts festgestellt wird. Eine reine Änderung ist entweder eine Änderung eines primitiven Eingabewerts (String, Number, Boolean, Symbol) oder einer geänderten Objektreferenz (Datum, Array, Funktion, Objekt).
Angular ignoriert Änderungen in (zusammengesetzten) Objekten. Es wird keine reine Pipe aufgerufen, wenn Sie einen Eingabemonat ändern, einem Eingabearray hinzufügen oder eine Eingabeobjekteigenschaft aktualisieren.
Dies mag restriktiv erscheinen, ist aber auch schnell. Eine Objektreferenzprüfung ist schnell - viel schneller als eine gründliche Prüfung auf Unterschiede -, sodass Angular schnell feststellen kann, ob sowohl die Pipe-Ausführung als auch eine Ansichtsaktualisierung übersprungen werden können.
Aus diesem Grund ist eine reine Pipe vorzuziehen, wenn Sie mit der Änderungserkennungsstrategie leben können. Wenn Sie nicht können, können Sie das unreine Rohr verwenden.
quelle
Anstatt rein zu machen: falsch. Sie können den Wert in der Komponente tief kopieren und durch this.students = Object.assign ([], NEW_ARRAY) ersetzen. Dabei ist NEW_ARRAY das geänderte Array.
Es funktioniert für Winkel 6 und sollte auch für andere Winkelversionen funktionieren.
quelle
Eine Problemumgehung: Importieren Sie Pipe manuell in den Konstruktor und rufen Sie die Transformationsmethode mit dieser Pipe auf
Eigentlich brauchst du nicht mal eine Pfeife
quelle
Fügen Sie dem zusätzlichen Parameter Pipe hinzu und ändern Sie ihn direkt nach dem Ändern des Arrays. Selbst bei einer reinen Pipe wird die Liste aktualisiert
Artikel von Gegenständen lassen | Rohr: param
quelle
In diesem Anwendungsfall habe ich meine Pipe in ts-Datei zum Filtern von Daten verwendet. Es ist viel besser für die Leistung als die Verwendung von reinen Rohren. Verwenden Sie in ts wie folgt:
quelle
unreine Rohre herzustellen, ist bei der Leistung kostspielig. Erstellen Sie also keine unreinen Rohre, sondern ändern Sie die Referenz der Datenvariablen, indem Sie bei Datenänderungen eine Kopie der Daten erstellen und die Referenz der Kopie in der ursprünglichen Datenvariablen neu zuweisen.
quelle