Pour bien comprendre le problème et les solutions possibles, nous devons discuter de la détection de changement angulaire - pour les tuyaux et les composants.
Détection de changement de tuyau
Tuyaux sans état / purs
Par défaut, les tubes sont sans état / purs. Les tuyaux sans état / purs transforment simplement les données d'entrée en données de sortie. Ils ne se souviennent de rien, donc ils n'ont aucune propriété - juste une transform()
méthode. Angular peut donc optimiser le traitement des tuyaux sans état / purs: si leurs entrées ne changent pas, les tuyaux n'ont pas besoin d'être exécutés lors d'un cycle de détection de changement. Pour un tuyau tel que {{power | exponentialStrength: factor}}
, power
et factor
sont des entrées.
Pour cette question, "#student of students | sortByName:queryElem.value"
, students
et queryElem.value
sont entrées, et le tuyau sortByName
est sans état / pur. students
est un tableau (référence).
- Lorsqu'un étudiant est ajouté, la référence du tableau ne change pas -
students
ne change pas - donc le tube sans état / pur n'est pas exécuté.
- Quand quelque chose est tapé dans l'entrée du filtre,
queryElem.value
change, donc le tube sans état / pur est exécuté.
Une façon de résoudre le problème du tableau est de changer la référence du tableau chaque fois qu'un étudiant est ajouté - c'est-à-dire de créer un nouveau tableau chaque fois qu'un étudiant est ajouté. Nous pourrions le faire avec concat()
:
this.students = this.students.concat([{name: studentName}]);
Bien que cela fonctionne, notre addNewStudent()
méthode ne devrait pas avoir à être implémentée d'une certaine manière simplement parce que nous utilisons un tube. Nous voulons utiliser push()
pour ajouter à notre tableau.
Tuyaux avec état
Les tuyaux avec état ont un état - ils ont normalement des propriétés, pas seulement une transform()
méthode. Ils peuvent avoir besoin d'être évalués même si leurs entrées n'ont pas changé. Lorsque nous spécifions qu'un tube est avec état / non pur - pure: false
- alors chaque fois que le système de détection de changement d'Angular vérifie un composant pour les modifications et que ce composant utilise un tube avec état, il vérifie la sortie du tube, si son entrée a changé ou non.
Cela ressemble à ce que nous voulons, même si c'est moins efficace, car nous voulons que le tube s'exécute même si la students
référence n'a pas changé. Si nous rendons simplement le tube avec état, nous obtenons une erreur:
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
Selon la réponse de @ drewmoore , "cette erreur se produit uniquement en mode dev (qui est activé par défaut à partir de la version bêta-0). Si vous appelez enableProdMode()
lors du démarrage de l'application, l'erreur ne sera pas levée." La documentation pour l'ApplicationRef.tick()
état:
En mode développement, tick () effectue également un deuxième cycle de détection de changement pour s'assurer qu'aucun autre changement n'est détecté. Si des modifications supplémentaires sont prises en compte au cours de ce deuxième cycle, les liaisons dans l'application ont des effets secondaires qui ne peuvent pas être résolus en une seule passe de détection des modifications. Dans ce cas, Angular renvoie une erreur, car une application Angular ne peut avoir qu'une seule passe de détection de changement pendant laquelle toute la détection de changement doit se terminer.
Dans notre scénario, je pense que l'erreur est fausse / trompeuse. Nous avons un tube avec état, et la sortie peut changer chaque fois qu'elle est appelée - cela peut avoir des effets secondaires et ce n'est pas grave. NgFor est évalué après le tube, donc cela devrait fonctionner correctement.
Cependant, nous ne pouvons pas vraiment développer avec cette erreur lancée, donc une solution de contournement consiste à ajouter une propriété de tableau (c'est-à-dire, un état) à l'implémentation du tube et à toujours renvoyer ce tableau. Voir la réponse de @ pixelbits pour cette solution.
Cependant, nous pouvons être plus efficaces, et comme nous le verrons, nous n'aurons pas besoin de la propriété array dans l'implémentation du tube, et nous n'aurons pas besoin d'une solution de contournement pour la détection de double changement.
Détection de changement de composant
Par défaut, à chaque événement du navigateur, la détection de changement angulaire passe par chaque composant pour voir s'il a changé - les entrées et les modèles (et peut-être d'autres choses?) Sont vérifiés.
Si nous savons qu'un composant ne dépend que de ses propriétés d'entrée (et des événements de modèle), et que les propriétés d'entrée sont immuables, nous pouvons utiliser la onPush
stratégie de détection des changements beaucoup plus efficace . Avec cette stratégie, au lieu de vérifier chaque événement du navigateur, un composant est vérifié uniquement lorsque les entrées changent et lorsque les événements de modèle se déclenchent. Et, apparemment, nous n'obtenons pas cette Expression ... has changed after it was checked
erreur avec ce paramètre. Cela est dû au fait qu'un onPush
composant n'est pas vérifié à nouveau tant qu'il n'est pas à nouveau "marqué" ( ChangeDetectorRef.markForCheck()
). Ainsi, les liaisons de modèle et les sorties de canal avec état ne sont exécutées / évaluées qu'une seule fois. Les tuyaux sans état / purs ne sont toujours pas exécutés à moins que leurs entrées ne changent. Nous avons donc encore besoin d'un tube avec état ici.
C'est la solution proposée par @EricMartinez: pipe avec état avec onPush
détection de changement. Voir la réponse de @ caffinatedmonkey pour cette solution.
Notez qu'avec cette solution, la transform()
méthode n'a pas besoin de renvoyer le même tableau à chaque fois. Je trouve cela un peu étrange cependant: un tube avec état sans état. En y réfléchissant un peu plus ... le tube avec état devrait probablement toujours renvoyer le même tableau. Sinon, il ne peut être utilisé qu'avec des onPush
composants en mode développement.
Donc après tout ça, je pense que j'aime une combinaison des réponses de @ Eric et @ pixelbits: pipe avec état qui renvoie la même référence de tableau, avec onPush
détection de changement si le composant le permet. Étant donné que le tube avec état renvoie la même référence de tableau, le tube peut toujours être utilisé avec des composants qui ne sont pas configurés avec onPush
.
Plunker
Cela deviendra probablement un idiome Angular 2: si un tableau alimente un tube et que le tableau peut changer (les éléments du tableau qui sont, pas la référence du tableau), nous devons utiliser un tube avec état.
pure:false
votre Pipe etchangeDetection: ChangeDetectionStrategy.OnPush
votre Component.