How to clear/remove observable bindings in Knockout.js?
I'm building functionality onto a webpage which the user can perform multiple times. Through the user's action, an object/model is created and applied to HTML using ko.applyBindings().
The data-bound HTML is created through jQuery templates.
So far so good.
When I repeat this step by creating a second object/model and call ko.applyBindings() I encounter two problems:
- The markup shows the previous object/model as well as the new object/model.
- A javascript error occurs relating to one of the properties in the object/model, although it's still rendered in the markup.
To get around this problem, after the first pass I call jQuery's .empty() to remove the templated HTML which contains all the data-bind attributes, so that it's no longer in the DOM. When the user starts the process for the second pass the data-bound HTML is re-added to the DOM.
But like I said, when the HTML is re-added to the DOM and re-bound to the new object/model, it still includes data from the the first object/model, and I still get the JS error which doesn't occur during the first pass.
The conclusion appears to be that Knockout is holding on to these bound properties, even though the markup is removed from the DOM.
So what I'm looking for is a means of removing these bound properties from Knockout; telling knockout that there is no longer an observable model. Is there a way to do this?
EDIT
The basic process is that the user uploads a file; the server then responds with a JSON object, the data-bound HTML is added to the DOM, then the JSON object model is bound to this HTML using
mn.AccountCreationModel = new AccountViewModel(jsonData.Account);
ko.applyBindings(mn.AccountCreationModel);
Once the user has made some selections on the model, the same object is posted back to the server, the data-bound HTML is removed from then DOM, and I then have the following JS
mn.AccountCreationModel = null;
When the user wishes to do this once more, all these steps are repeated.
I'm afraid the code is too 'involved' to do a jsFiddle demo.
Have you tried calling knockout's clean node method on your DOM element to dispose of the in memory bound objects?
var element = $('#elementId')[0];
ko.cleanNode(element);
Then applying the knockout bindings again on just that element with your new view models would update your view binding.
For a project I'm working on, I wrote a simple ko.unapplyBindings
function that accepts a jQuery node and the remove boolean. It first unbinds all jQuery events as ko.cleanNode
method doesn't take care of that. I've tested for memory leaks, and it appears to work just fine.
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]);
}
};
You could try using the with binding that knockout offers: http://knockoutjs.com/documentation/with-binding.html The idea is to use apply bindings once, and whenever your data changes, just update your model.
Lets say you have a top level view model storeViewModel, your cart represented by cartViewModel, and a list of items in that cart - say cartItemsViewModel.
You would bind the top level model - the storeViewModel to the whole page. Then, you could separate the parts of your page that are responsible for cart or cart items.
Lets assume that the cartItemsViewModel has the following structure:
var actualCartItemsModel = { CartItems: [
{ ItemName: "FirstItem", Price: 12 },
{ ItemName: "SecondItem", Price: 10 }
] }
The cartItemsViewModel can be empty at the beginning.
The steps would look like this:
-
Define bindings in html. Separate the cartItemsViewModel binding.
<div data-bind="with: cartItemsViewModel"> <div data-bind="foreach: CartItems"> <span data-bind="text: ItemName"></span> <span data-bind="text: Price"></span> </div> </div>
-
The store model comes from your server (or is created in any other way).
var storeViewModel = ko.mapping.fromJS(modelFromServer)
-
Define empty models on your top level view model. Then a structure of that model can be updated with actual data.
storeViewModel.cartItemsViewModel = ko.observable(); storeViewModel.cartViewModel = ko.observable();
-
Bind the top level view model.
ko.applyBindings(storeViewModel);
-
When the cartItemsViewModel object is available then assign it to the previously defined placeholder.
storeViewModel.cartItemsViewModel(actualCartItemsModel);
If you would like to clear the cart items:
storeViewModel.cartItemsViewModel(null);
Knockout will take care of html - i.e. it will appear when model is not empty and the contents of div (the one with the "with binding") will disappear.
I have to call ko.applyBinding each time search button click, and filtered data is return from server, and in this case following work for me without using ko.cleanNode.
I experienced, if we replace foreach with template then it should work fine in case of collections/observableArray.
You may find this scenario useful.
<ul data-bind="template: { name: 'template', foreach: Events }"></ul>
<script id="template" type="text/html">
<li><span data-bind="text: Name"></span></li>
</script>