NgFor ne met pas à jour les données avec Pipe en Angular2

dans ce scénario, je montre une liste d'étudiants (tableau) à la vue avec ngFor :

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

c'est merveilleux qu'il se mette à jour chaque fois que j'ajoute d'autres étudiants à la liste.

cependant, quand je lui donne un pipe à filter par le nom de l'étudiant,

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

il ne met pas à jour la liste jusqu'à ce que je tape quelque chose dans le champ de filtrage du nom de l'étudiant.

voici un lien vers 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;
    }
}

97
demandé sur Narendra Jadhav 2015-12-24 21:07:55

4 réponses

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 Conduite

Pipes apatrides / pures

par défaut, les pipes sont apatrides/pures. Les pipes apatrides/pures transforment simplement les données d'entrée en données de sortie. Ils ne se souviennent de rien, donc ils n'ont pas de propriétés – juste une méthode transform() . Angulaire peut donc optimiser le traitement des pipes apatrides/pures: si leurs entrées ne changent pas, les pipes n'ont pas besoin d'être exécutées pendant un cycle de détection de changement. Pour un tuyau comme {{power | exponentialStrength: factor}} , power et factor sont des entrées.

Pour cette question, "#student of students | sortByName:queryElem.value" , students et queryElem.value sont des entrées, et la pipe sortByName est sans état pur. students est un tableau (référence).

  • Lorsqu'un étudiant est ajouté, le tableau de référence ne change pas, students ne change pas, donc les apatrides/pure pipe n'est pas exécutée.
  • quand quelque chose est tapé dans l'entrée du filtre, queryElem.value change, donc le tuyau apatride/pur est exécuté.

une façon de corriger la question du tableau est de changer la référence du tableau chaque fois qu'un élève est ajouté – c'est-à-dire de créer un nouveau tableau chaque fois qu'un élève est ajouté. Nous pourrions le faire avec concat() :

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

bien que cela fonctionne, notre méthode addNewStudent() ne devrait pas être implémentée d'une certaine manière juste parce que nous utilisons un tuyau. Nous voulons utiliser push() pour ajouter à notre tableau.

Pipes Stateful

les pipes ont un État -- elles ont normalement des propriétés, pas seulement une méthode transform() . Ils peuvent avoir besoin d'être évalués même si leurs intrants n'ont pas changé. Lorsque nous spécifions qu'un le tuyau est stateful / non-pur – pure: false - alors chaque fois que le système de détection de changement D'Angular vérifie un composant pour des changements et que ce composant utilise un tuyau stateful, il vérifiera la sortie du tuyau, si son entrée a changé ou non.

cela ressemble à ce que nous voulons, même si c'est moins efficace, puisque nous voulons que la pipe s'exécute même si la référence students n'a pas changé. Si nous rendons simplement la pipe stable, 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 réponse de @drewmoore , " cette erreur ne se produit qu'en mode dev (qui est activé par défaut à partir de bêta-0). Si vous appelez enableProdMode() lors de l'amorçage de l'application, l'erreur ne sera pas lancée."Le docs pour ApplicationRef.tick() de l'é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 d'autres changements sont repris au cours de ce second cycle, les fixations dans l'application ont des effets secondaires qui ne peuvent pas être résolus en une seule passe de détection de changement. Dans ce cas, Angular lance une erreur, car une application angulaire ne peut avoir qu'un seul passage de détection de changement pendant lequel toute détection de changement doit être terminée.

Dans notre scénario, je crois que l'erreur est fausse ou trompeuse. Nous avons un tuyau stateful, et la sortie peut changer à chaque fois qu'il est appelé – il peut avoir des effets secondaires, et c'est correct. NgFor est évalué après le tuyau, donc il devrait fonctionner très bien.

Cependant, nous ne pouvons pas vraiment développer avec cette erreur étant lancé, donc une solution est d'ajouter une propriété de tableau (i.e., l'État) à l'implémentation de pipe et toujours retourner 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 la mise en œuvre de pipe, et nous pas besoin de solution de contournement pour la détection de double changement.

Détection De Changement De Composant

par défaut, sur chaque événement de navigateur, la détection de changement angulaire passe par chaque composant pour voir s'il a changé – entrées et gabarits (et peut-être d'autres choses?) sont vérifiées.

Si nous savons qu'un composant ne dépend que de ses propriétés d'entrée (et le modèle), et que les propriétés d'entrée sont immuables, nous pouvons utiliser l' stratégie de détection des changements plus efficace onPush . Avec cette stratégie, au lieu de vérifier chaque événement du navigateur, un composant est vérifié seulement quand les entrées changent et quand les événements du modèle se déclenchent. Et, apparemment, nous ne comprenons pas cette erreur Expression ... has changed after it was checked avec ce paramètre. C'est parce qu'un onPush composant n'est pas vérifié à nouveau jusqu'à ce qu'il est "marqué" ( ChangeDetectorRef.markForCheck() ). Ainsi, les fixations de Template et les sorties de pipe stateful ne sont exécutées/évaluées qu'une seule fois. Pipes apatrides / pures ne sont toujours pas exécutés à moins que leurs entrées changent. Nous avons donc encore besoin d'un stateful pipe ici.

c'est la solution suggérée par @EricMartinez: tuyau statable avec détection de changement onPush . Voir la réponse de @caffinatemonkey pour cette solution.

notez qu'avec cette solution la méthode transform() n'a pas besoin de retourner le même tableau à chaque fois. Je trouve cela un peu étrange cependant: un tuyau stateful sans état. D'y penser un peu plus... le la pipe stateful devrait probablement toujours retourner le même tableau. Sinon, il ne peut être utilisé qu'avec des composants onPush en mode dev.


après tout cela, je pense que j'aime une combinaison de réponses de @Eric's et @pixelbits: pipe stateful qui renvoie la même référence de tableau, avec onPush détection de changement si le composant le permet. Depuis une pipe retourne la même matrice de référence, le tuyau peut être utilisée avec composants qui ne sont pas configurés avec onPush .

Plunker

ceci deviendra probablement un idiome 2 angulaire: si un tableau alimente un tuyau, et que le tableau peut changer (les éléments dans le tableau qui est, pas la référence du tableau), nous devons utiliser un tuyau stateful.

124
répondu Mark Rajcok 2017-05-23 12:34:47

comme Eric Martinez l'a souligné dans les commentaires, l'ajout de pure: false à votre décorateur Pipe et changeDetection: ChangeDetectionStrategy.OnPush à votre décorateur Component réglera votre problème. voici un plunkr de travail. Changer de ChangeDetectionStrategy.Always , fonctionne aussi. Voici pourquoi.

selon le guide angular2 sur les pipes :

Les Pipes

sont apatrides par défaut. Nous devons déclarer un tuyau être stateful en définissant la propriété pure du décorateur @Pipe à false . Ce réglage indique au système de détection de changement D'Angular de vérifier la sortie de ce tuyau à chaque cycle, si son entrée a changé ou non.

comme pour ChangeDetectionStrategy , par défaut, toutes les fixations sont vérifiées à chaque cycle. Lorsqu'un tuyau pure: false est ajouté, je crois que la méthode de détection des changements passe de CheckAlways à CheckOnce pour des raisons de performance. Avec OnPush , les consolidations pour le composant ne sont vérifiées que lorsqu'une propriété d'entrée change ou lorsqu'un événement est déclenché. Pour plus d'informations sur les détecteurs de changement, une partie importante de angular2 , consultez les liens suivants:

24
répondu 0xcaff 2015-12-25 02:30:46

Démo Plunkr

vous n'avez pas besoin de changer la stratégie de section changée. La mise en place d'une Pipe statative est suffisante pour que tout fonctionne.

il s'agit d'un tuyau réglable (aucun autre changement n'a été apporté):

@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
répondu pixelbits 2015-12-25 22:46:19

De la angulaire de la documentation

pipes pures et impures

il y a deux catégories de pipes: pure et impure. Les Pipes sont pures par défaut. Toutes les pipes que vous avez vues jusqu'à présent étaient pures. Vous rendez un tuyau impur en mettant son drapeau pur à false. Vous pourriez rendre le FlyingHeroesPipe impur comme ceci:

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

avant de faire ça, comprendre la différence entre pur et impur, en commençant par un tuyau pur.

pipes pures Angular n'exécute un tube pur que lorsqu'il détecte un changement pur à la valeur d'entrée. Un changement pur est soit un changement à une valeur d'entrée primitive (chaîne, Nombre, booléen, symbole) ou une référence d'objet changée (Date, tableau, fonction, objet).

Angulaire ignore les changements à l'intérieur (composite) des objets. Il ne sera pas appeler un tuyau pur si vous changez un mois d'entrée, ajouter à un tableau d'entrées, ou mettre à jour une propriété d'objet d'entrée.

cela peut sembler restrictif, mais c'est aussi rapide. Une vérification de référence d'objet est rapide-beaucoup plus rapide qu'une vérification en profondeur pour les différences-donc angulaire peut déterminer rapidement si elle peut sauter à la fois l'exécution du tuyau et une mise à jour de la vue.

pour cette raison, un tuyau pur est préférable quand vous pouvez vivre avec la stratégie de détection de changement. Quand tu ne peux pas, tu peux utiliser le tuyau impur.

7
répondu Sumit Jaiswal 2017-07-28 09:37:03