Unapparent Parent in Knockout Binding Context

When working with KnockoutJS, the with binding allows you to change the current binding context. So if you have a nested data structure, instead of having to binding to properties such as subViewModel.property1 and subViewModel.property2, you can put them inside a with binding and then refer to them directly as property1 and property2, respectively.

This is quite useful in keeping your views from getting too cluttered when your viewmodel structure gets large. However, if you jump down more than one level in a single the with binding, it’s not immediately apparent what the $parent context will refer to.

Let’s look at an example. Here is our viewmodel structure. We have a house, inside the house we have a basement, and inside the basement we have a sofa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var HouseViewModel = function() {
var self = this;
self.name = "House";
self.basement = new BasementViewModel();
};

var BasementViewModel = function() {
var self = this;
self.name = "Basement";
self.sofa = new SofaViewModel();
};

var SofaViewModel = function() {
var self = this;
self.name = "Sofa";
};

ko.applyBindings(new HouseViewModel());

Here is some corresponding markup that makes use of the with binding:

1
2
3
4
<!-- ko with: basement -->
<div>Where am I? <span data-bind="text:name"></span></div>
<div>My Parent is <span data-bind="text:$parent.name"></span></div>
<!-- /ko -->

As expected, $parent refers to the HouseViewModel, so the output looks like this:

1
2
Where am I? Basement
My Parent is House

So what happens if you jump two levels down when using the with binding, and then try to access a property on the $parent context?

1
2
3
4
<!-- ko with: basement.sofa -->
<div>Where am I? <span data-bind="text:name"></span></div>
<div>My Parent is <span data-bind="text:$parent.name"></span></div>
<!-- /ko -->

When I first tried this, I expected that since the current binding context was the sofaViewModel, that $parent would refer to the basementViewModel. Thus, I was expecting this output:

1
2
Where am I? Sofa
My Parent is Basement

but instead I saw this:

1
2
Where am I? Sofa
My Parent is Basement

What just happened?

As it turns out, the $parent viewmodel is not referring to the parent viewmodel from a javascript perspective, which is what I was expecting. Rather, it refers to the next outer layer of binding context as seen in the markup. In the markup, I jumped directly from the HouseViewModel to the SofaViewModel when I bound to with: basement.sofa, and so referring to $parent similarly jumped me directly from the SofaViewModel back to the HouseViewModel, skipping the BasementViewModel.

Try it out at http://jsfiddle.net/tlarson/ujm9p