I’ve recently noticed a couple concerning things when using the KO mapping plugin to update data on a page. The first I believe is now fixed in 2.1.1 the second shown below still exists:
I have a simple model. The problem is with an array of addresses it contains. It seems when I use the mapping plugin it tracks 2 elements in the array when there is actually only ever one. I’m not certain if this a problem w/ my code or the mapping plugin. Please consider the following simple example:
//Retrieved thru AJAX
var serverData = { name: "Bob", Addresses: [{ AddressLine: "", City: "", PostalCode: "", StateID: 10}] };
load(serverData);
//Seems OK at this point
//this.vm.__ko_mapping__.mappedProperties shows properties for Addresses[0] & name which makes sense
//Now some time goes by and we want to update the bound VM w/ new data from the server
load(serverData);
//Problem!
//this.vm.__ko_mapping__.mappedProperties shows properties for Addresses[0] & Addresses[1]
//But there is no Addresses[1]!!
//Lets simulate an update of data (1 more time)
load(serverData);
//Interestingly it doesn't get any worse, still just Addresses[0] & Addresses[1]
function load(d)
{
if (this.vm) //Refresh existing VM
{
ko.mapping.fromJS(serverData, vm);
}
else //On 1st Load have mapping create the VM and bind it
{
this.vm = ko.mapping.fromJS(serverData); //Mapping creates object from server data
ko.applyBindings(this.vm, $("body")[0]);
}
}
The mapping plugin allows to define a callback that returns keys for elements in an array. (see “Uniquely identifying objects using keys” at http://knockoutjs.com/documentation/plugins-mapping.html). This is used to determine whether an object is new or old. There are three possible states: A new object gets added to the array, an already existing element retains in the array (but gets updated) or an existing element gets removed from the array since it is no longer present in the new dataset. (Those states get actually determined by the utility function ko.utils.compareArrays)
Here the correct state would be “retaining”, but since you’re not providing unique keys for the addresses in the array, the mapping plugin has no clue that those entries are actually the same – therefore the state “remove” gets assigned to the present object and the state “add” to the new one.
This results in a list with all elements that need attention – the present one has key “0”, the new one key “1”, hence the weird entries in “mappedProperties”. I think this can be considered a bug, but it’s a really tricky one. And it’s not a true memory leak since the number of ghost entries will always be
numPreviousEntries.Here is a fiddle demonstrating that the use of unique keys (can be anything if you have not more than one row, so I used the state id) indeed resolves this issue: http://jsfiddle.net/xTHFg/4/