Comment effacer / supprimer les fixations observables dans Knockout.js?

je construis des fonctionnalités sur une page Web que l'utilisateur peut exécuter plusieurs fois. Grâce à l'action de l'utilisateur, un objet/modèle est créé et appliqué au HTML en utilisant ko.applyBindings ().

le HTML relié aux données est créé par les gabarits de jQuery.

pour l'instant tout va bien.

quand je répète cette étape en créant un second objet/modèle et appelle ko.applyBindings() je rencontre deux problèmes:

  1. Le balisage indique l'objet précédent modèle, ainsi que le nouvel objet/modèle.
  2. une erreur javascript se produit concernant une des propriétés de l'objet/du modèle, bien qu'elle soit encore rendue dans le markup.

pour contourner ce problème, après la première passe, j'appelle jQuery .empty() pour supprimer le HTML Templé qui contient tous les attributs data-bind, de sorte qu'il ne soit plus dans le DOM. Quand l'utilisateur commence le processus pour le second passage, le HTML relié aux données est ajouté de nouveau au DOM.

mais comme je l'ai dit, quand le HTML est ré-ajouté au DOM et re-lié au nouvel objet/modèle, il inclut toujours des données du premier objet/modèle, et je reçois toujours l'erreur JS qui ne se produit pas lors de la première passe.

la conclusion semble être que Knockout s'accroche à ces propriétés liées, même si le markup est retiré du DOM.

Donc, ce que je cherche est un moyen de supprimer ces propriétés liées de Knockout; disant knockout qu'il n'y a plus de modèle observable. Est-il un moyen de faire cela?

MODIFIER

le processus de base est que l'utilisateur télécharge un fichier; le serveur répond alors avec un objet JSON, le HTML lié aux données est ajouté au DOM, puis le modèle d'objet JSON est lié à ce HTML en utilisant

mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);

Une fois que l'Utilisateur a fait quelques sélections sur le modèle, le même objet est posté de nouveau sur le serveur, le HTML lié aux données est retiré de puis DOM, et j'ai alors le suivant JS

mn.AccountCreationModel = null;

Lorsque l'utilisateur souhaite le faire une fois de plus, toutes ces étapes sont répétées.

j'ai bien peur que le code soit trop "impliqué" pour faire une démo de jsFiddle.

110
demandé sur awj 2012-04-06 23:46:42

9 réponses

avez-vous essayé d'appeler la méthode clean node de knockout sur votre élément DOM pour disposer des objets liés en mémoire?

var element = $('#elementId')[0]; 
ko.cleanNode(element);

alors appliquer les fixations knockout à nouveau sur cet élément avec vos nouveaux modèles de vue mettrait à jour votre reliure de vue.

162
répondu KodeKreachor 2012-11-22 22:00:33

pour un projet sur lequel je travaille, j'ai écrit une simple fonction ko.unapplyBindings qui accepte un noeud jQuery et le booléen remove. Il déborde d'abord tous les événements jQuery comme ko.cleanNode méthode ne prend pas soin de cela. J'ai vérifié les fuites de mémoire, et il semble que ça fonctionne très bien.

ko.unapplyBindings = function ($node, remove) {
    // unbind events
    $node.find("*").each(function () {
        $(this).unbind();
    });

    // Remove KO subscriptions and references
    if (remove) {
        ko.removeNode($node[0]);
    } else {
        ko.cleanNode($node[0]);
    }
};
30
répondu Michael Berkompas 2012-11-19 18:30:47

vous pouvez essayer d'utiliser l'avec reliure que knockout offre: http://knockoutjs.com/documentation/with-binding.html L'idée est d'utiliser Appliquer des consolidations une fois, et chaque fois que vos données changent, mettez simplement à jour votre modèle.

disons que vous avez une vue de haut niveau modèle storeViewModel, votre panier représenté par cartViewModel, et une liste d'articles dans ce panier - dire cartItemsViewModel.

vous lieriez le modèle de haut niveau - le storeViewModel à toute la page. Ensuite, vous pouvez séparer les éléments de votre page, qui sont responsables de chariot ou panier articles.

laisse supposer que le nouveau modèle cartitemsview a la structure suivante:

var actualCartItemsModel = { CartItems: [
  { ItemName: "FirstItem", Price: 12 }, 
  { ItemName: "SecondItem", Price: 10 }
] }

le nouveau modèle cartitemsview peut être vide au début.

Les étapes ressemblerait à ceci:

  1. définir les reliures en html. Séparer le nouveau modèle cartitemsview contraignant.

      
        <div data-bind="with: cartItemsViewModel">
          <div data-bind="foreach: CartItems">
            <span data-bind="text: ItemName"></span>
            <span data-bind="text: Price"></span>
          </div>
        </div>
      
    
  2. le modèle store provient de votre serveur (ou est créé de toute autre manière).

    var storeViewModel = ko.mapping.fromJS(modelFromServer)

  3. définissez les modèles vides sur votre modèle de vue de haut niveau. Ensuite, une structure de ce modèle peut être mise à jour avec données réelles.

      
        storeViewModel.cartItemsViewModel = ko.observable();
        storeViewModel.cartViewModel = ko.observable();
     
    
  4. lient le modèle de vue de niveau supérieur.

    ko.applyBindings(storeViewModel);

  5. lorsque l'objet cartItemsViewModel est disponible, alors assignez-le au placeholder défini précédemment.

    storeViewModel.cartItemsViewModel(actualCartItemsModel);

si vous souhaitez vider le panier articles: storeViewModel.cartItemsViewModel(null);

Knockout prendra soin de html-i.e. il apparaîtra quand le model n'est pas vide et le contenu de div (celui avec Le "with binding") disparaître.

12
répondu Sylwester Gryzio 2013-07-23 07:45:59

je dois appeler ko.applyBinding chaque fois que le bouton de recherche clique, et les données filtrées est de retour à partir du serveur, et dans ce cas suivant le travail pour moi sans utiliser ko.cleanNode.

j'ai expérimenté, si nous remplaçons foreach avec modèle alors il devrait fonctionner très bien en cas de collections/observableArray.

, Vous pouvez trouver ce scénario utile.

<ul data-bind="template: { name: 'template', foreach: Events }"></ul>

<script id="template" type="text/html">
    <li><span data-bind="text: Name"></span></li>
</script>
9
répondu aamir sajjad 2013-01-16 06:14:51

au lieu d'utiliser les fonctions internes de KO et de s'occuper de l'enlèvement du gestionnaire d'événements général de JQuery, une bien meilleure idée est d'utiliser des reliures with ou template . Quand vous faites cela, ko re-crée cette partie de DOM et donc il est automatiquement nettoyé. Ceci est également recommandé, voir ici: https://stackoverflow.com/a/15069509/207661 .

6
répondu ShitalShah 2017-05-23 10:31:29

je pense qu'il pourrait être préférable de garder la liaison tout le temps, et simplement mettre à jour les données qui y sont associées. J'ai rencontré ce problème, et j'ai trouvé que simplement appeler en utilisant la méthode .resetAll() sur le tableau dans lequel je gardais mes données était le moyen le plus efficace pour le faire.

fondamentalement, vous pouvez commencer avec un var global qui contient des données à rendre via le ViewModel:

var myLiveData = ko.observableArray();

il m'a fallu un certain temps pour réaliser que je on ne pouvait pas faire de myLiveData un tableau normal -- la partie ko.oberservableArray était importante.

, Alors vous pouvez aller de l'avant et faire ce que vous voulez myLiveData . Par exemple, faites un $.getJSON appel:

$.getJSON("http://foo.bar/data.json?callback=?", function(data) {
    myLiveData.removeAll();
    /* parse the JSON data however you want, get it into myLiveData, as below */
    myLiveData.push(data[0].foo);
    myLiveData.push(data[4].bar);
});

une fois que vous avez fait cela, vous pouvez aller de l'avant et appliquer des fixations en utilisant votre modèle de vue comme d'habitude:

function MyViewModel() {
    var self = this;
    self.myData = myLiveData;
};
ko.applyBindings(new MyViewModel());

alors dans le HTML il suffit d'utiliser myData comme vous le feriez normalement.

De cette façon, vous pouvez juste muck avec myLiveData de n'importe quelle fonction. Par exemple, si vous voulez mettre à jour toutes les quelques secondes, enveloppez cette ligne $.getJSON dans une fonction et appelez setInterval dessus. Vous n'aurez jamais besoin d'enlever la reliure aussi longtemps que vous vous rappelez de garder la ligne myLiveData.removeAll(); .

à moins que vos données ne soient vraiment énormes, les utilisateurs ne seront même pas en mesure de remarquer le temps entre la réinitialisation du tableau et l'ajout des données les plus récentes.

4
répondu Zac 2013-10-22 05:23:55

j'ai eu un problème de fuite de mémoire récemment et ko.cleanNode(element); ne le ferait pas pour moi - ko.removeNode(element); l'a fait. Javascript + Knockout.js memory leak - comment s'assurer que l'objet est détruit?

2
répondu Matas Vaitkevicius 2017-05-23 11:47:20

avez-vous pensé à ceci:

try {
    ko.applyBindings(PersonListViewModel);
}
catch (err) {
    console.log(err.message);
}

j'ai trouvé ça parce que dans Knockout, j'ai trouvé ce code

    var alreadyBound = ko.utils.domData.get(node, boundElementDomDataKey);
    if (!sourceBindings) {
        if (alreadyBound) {
            throw Error("You cannot apply bindings multiple times to the same element.");
        }
        ko.utils.domData.set(node, boundElementDomDataKey, true);
    }

donc pour moi ce n'est pas vraiment une question que son déjà lié, c'est que l'erreur n'a pas été attrapé et traité...

1
répondu ozzy432836 2015-05-12 15:35:55

j'ai trouvé que si le modèle de vue contient de nombreuses fixations div, la meilleure façon d'effacer le ko.applyBindings(new someModelView); est d'utiliser: ko.cleanNode($("body")[0]); cela vous permet d'appeler un nouveau ko.applyBindings(new someModelView2); de manière dynamique sans que le souci du modèle de vue précédent soit toujours lié.

0
répondu Derrick Hampton 2013-07-03 15:43:14