web-dev-qa-db-fra.com

Comment puis-je utiliser les pseudovariables $ parent / $ root de knockout à l'intérieur d'un observable .computed ()?

Dans une expression de liaison knockout.js , je peux utiliser $data, $parent, et $root pseudovariables . Comment puis-je obtenir l'équivalent de ces pseudovariables lorsque j'utilise un ko.computed observable déclaré en JavaScript?

J'ai un viewmodel parent avec une collection d'enfants, et le viewmodel parent a un selectedChild observable. Étant donné que, je peux utiliser des expressions de liaison de données pour ajouter une classe CSS à l'enfant actuellement sélectionné:

<ul data-bind="foreach: children">
    <li data-bind="text: name,
                   css: {selected: $data === $root.selectedChild()},
                   click: $root.selectChild"></li>
</ul>
<script>
vm = {
    selectedChild: ko.observable(),
    children: [{name: 'Bob'}, {name: 'Ned'}],
    selectChild: function(child) { vm.selectedChild(child); }
};
ko.applyBindings(vm);
</script>

Mais mes modèles de vue vont devenir plus complexes, et je voudrais "suis-je sélectionné?" pour pouvoir faire plus que simplement ajouter une seule classe CSS à un seul élément. Je veux vraiment créer une propriété calculée isSelected sur le modèle de vue enfant, afin que je puisse ensuite ajouter d'autres propriétés calculées qui en dépendent.

J'ai essayé d'écrire du JavaScript qui fait référence à $data et $root, au cas où le knockout pourrait définir ces variables et les faire en quelque sorte être à portée quand il appelle ma fonction d'évaluateur computed:

{
  name: 'Bob',
  isSelected: ko.computed(function(){ return $data === $root.selectedChild(); })
}

Mais pas de chance: à l'intérieur de mon évaluateur function, les deux $data et $root sont undefined.

J'ai également essayé d'utiliser ko.contextFor dans mon évaluateur, car il donne accès à $data et $root. Malheureusement, dans ma fonction d'évaluateur, contextFor renvoie également toujours undefined. (De toute façon, je ne nourrissais pas de grands espoirs pour cette stratégie - on ne sait pas dans quelle mesure le KO serait capable de suivre les dépendances si je devais aller derrière son dos comme ça.)

Je pouvais toujours définir manuellement une propriété sur chaque viewmodel enfant qui se réfère au viewmodel parent. Mais je sais que le knockout a la capacité de le faire pour moi, et j'aimerais au moins explorer si je peux utiliser ses mécanismes avant d'aller écrire moi-même.

Il semble qu'il devrait être possible de traduire l'expression de liaison ci-dessus en un observable calculé - après tout, c'est ce que fait déjà le knockout :

L'autre astuce est que les liaisons déclaratives sont simplement implémentées en tant qu'observables calculés.

Mais comment gérer le $data et $root pseudovariables quand j'écris mon propre observable calculé?

59
Joe White

Les pseudovariables ne sont disponibles que dans le contexte de la liaison de données. Le modèle de vue lui-même ne devrait idéalement pas connaître ou avoir de dépendances sur la vue qui l'affiche.

Ainsi, lorsque vous ajoutez des observables calculés dans le modèle de vue, vous ne savez pas comment il sera lié (comme ce qui va être $ root). Un modèle de vue ou une partie d'un modèle de vue peut même être lié séparément à plusieurs zones de la page à différents niveaux, de sorte que les pseudo-variables seraient différentes selon l'élément avec lequel vous commencez.

Cela dépend de ce que vous essayez d'accomplir, mais si vous voulez que votre enfant ait un isSelected observable calculé qui indique si cet élément est le même que l'élément sélectionné sur le modèle de vue parent, alors vous devrez trouver un moyen de mettre le parent à la disposition de l'enfant.

Une option consiste à passer le parent dans la fonction constructeur de votre enfant. Vous n'avez même pas besoin d'ajouter le pointeur au parent en tant que propriété de l'enfant et vous pouvez simplement l'utiliser directement dans votre observable calculé.

Quelque chose comme:

var Item = function(name, parent) {
   this.name = ko.observable(name);  
   this.isSelected = ko.computed(function() {
       return this === parent.selectedItem();        
   }, this);
};

var ViewModel = function() {
   this.selectedItem = ko.observable();
   this.items = ko.observableArray([
       new Item("one", this),
       new Item("two", this),
       new Item("three", this)
       ]);
};

Échantillon ici: http://jsfiddle.net/rniemeyer/BuH7N/

Si tout ce qui vous intéresse est le statut sélectionné, vous pouvez le modifier pour passer une référence au selectedItem observable au constructeur enfant comme: http://jsfiddle.net/rniemeyer/R5MtC/

Si votre modèle de vue parent est stocké dans une variable globale, vous pouvez envisager de ne pas le transmettre à l'enfant et de l'utiliser directement comme: http://jsfiddle.net/rniemeyer/3drUL/ . Je préfère cependant transmettre la référence à l'enfant.

77
RP Niemeyer

D'après mon expérience, l'approche de @RP Niemeyer est correcte si Items vit pendant la durée de l'application. Sinon, cela peut entraîner des fuites de mémoire, car l'observable calculé de Item établit une dépendance inverse à partir de ViewModel. Encore une fois, c'est ok si vous ne vous débarrassez jamais d'objets Item. Mais si vous essayez de vous débarrasser des Items, ils ne seront pas récupérés car les knockouts auront toujours cette référence de dépendance inverse.

Vous pourriez vous assurer de disposer () du calcul, peut-être dans une méthode cleanup () sur Item qui est appelée lorsque l'élément disparaît, mais vous devez vous rappeler de le faire chaque fois que vous supprimez Items.

Au lieu de cela, pourquoi ne pas rendre Item un peu moins intelligent et demander à ViewModel de le dire quand il est sélectionné? Faites simplement de Item la isSelected() un ancien observable régulier, puis dans ViewModel abonnez-vous à selectedItem et mettez à jour à l'intérieur de cet abonnement.

Ou utilisez @RP Niemeyer solution pub/sub . (Pour être honnête, cette solution est apparue après sa réponse ici.) Vous devrez toujours nettoyer, car cela crée également des dépendances inverses. Mais au moins, il y a moins de couplage.

Voir la réponse à ma question récente sur ce même sujet pour plus de détails.

1
devguydavid