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
und factor
sind Eingänge.
Für diese Frage "#student of students | sortByName:queryElem.value"
, students
und queryElem.value
sind Eingänge, und das Rohr sortByName
ist zustandslos / rein. students
ist ein Array (Referenz).
- Wenn ein Schüler 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 den Filtereingang eingegeben wird,
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()
:
this.students = this.students.concat([{name: studentName}]);
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 verwenden push()
, 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:
EXCEPTION: Expression 'students | sortByName:queryElem.value in HelloWorld@7: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 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:
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 Änderungserkennungsdurchlauf behoben werden können. In diesem Fall gibt Angular einen Fehler aus, da eine Angular-Anwendung nur einen Änderungserkennungsdurchlauf haben kann, während dessen die gesamte Änderungserkennung abgeschlossen sein muss.
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 diesen Expression ... has changed after it was checked
Fehler mit dieser Einstellung nicht. Dies liegt daran, dass eine onPush
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 mit onPush
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 sind onPush
.
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.
pure:false
in Ihrem Rohr undchangeDetection: ChangeDetectionStrategy.OnPush
in Ihrer Komponente hinzu.