DasBeasto DasBeasto - 2 months ago 17
Javascript Question

ko.mapping.fromJS not mapping nested value after value is set null

I am applying JSON data to my ViewModel using

ko.mapping.fromJS
. This JSON data has nested objects within it, like so:

var data = {
item : "#1234",
description: "This is item #1",
moreinfo: {
condition: "Good"
}
}


When I try to re-map new data to the object it works fine, updating all the values. However, if I remap the value of
moreinfo
to
null
because this item doesn't have moreInfo, like so:

var data2 = {
item : "#4567",
description: "This is item #2",
moreinfo:null
}


it doesn't update the DOM but instead keeps the previous value
condition:"Good"
. If I then update the value one more time to data that has
moreinfo
, like so:

var data3 = {
item : "#7890",
description: "This is item #3",
moreinfo: {
condition: "Bad"
}
}


it still updates the item and description, but still it doesn't update the DOM but instead keeps the value
condition:"Good"
.

Am I using mapping incorrectly or can you just not allow the value to become
null
?

Javascript:

var viewModel;
$("#button1").on("click", function(){
viewModel = ko.mapping.fromJS(data);
ko.applyBindings(viewModel, $("itemWrapper")[0]);
});
$("#button2").on("click", function(){
ko.mapping.fromJS(data2, {}, viewModel);
});
$("#button3").on("click", function(){
ko.mapping.fromJS(data3, {}, viewModel);
});


HTML:

<div id="itemWrapper">
<p data-bind="text: item"></p>
<p data-bind="text: description"></p>
<p data-bind="text: moreinfo.condition"></p>
</div>

<button id="button1">
Bind Data #1
</button>
<button id="button2">
Bind Data #2
</button>
<button id="button3">
Bind Data #3
</button>


JSfiddle: https://jsfiddle.net/hrgx5f1y/


  • If you applyBindings with Button #1, then map the null with #2, then map new values with #3 you will see the issue I describe.

  • If you applyBindings with Button #1, then map new values with #3, then map new values with #4 (none of these are null) it works perfectly.


Answer

The way that Knockout binding handlers work is that they hook up to an observable, and they respond when that observable changes. It does not bind to your binding expression but to your observable object So your binding:

<p data-bind="text: moreinfo.condition"></p>

...takes the condition property of moreinfo, which is an observable object and subscribes to it. When you do this:

 var data4 = {
   item : "#0000",
   description: "This is item #4",
   moreinfo: {
       condition: "Meh"
   }
 }
 ko.mapping.fromJS(data4, {}, viewModel);

...it works because Knockout can tie the structure of data4 to your view model and update that exact same observable object with the new value 'Meh'.

If you do this instead:

var data2 = {
  item : "#4567",
  description: "This is item #2",
  moreinfo:null
}
$("#button2").on("click", function(){
  ko.mapping.fromJS(data2, {}, viewModel);
});

...it is not updating that observable, but rather is updating the moreinfo property so it's null. Since a binding is to an observable object, not an expression, even if you update the view model so moreinfo isn't null anymore, the binding expression is not re-evaluated; your DOM is still bound to that same original observable.

You can work around this by having moreinfo as an observable, binding to it, and subsequently binding to condition; that way, if either updates, your DOM will update as expected. For example:

<!-- ko with:moreinfo -->
  <p data-bind="text: condition"></p> 
<!-- /ko -->