Tim Tim - 3 months ago 14
TypeScript Question

$parent ignored in knockout data binding

I have a knockout viewmodel (written in typescript) which follows a recursive structure. A simplified version is as follows:

class ReportGroupViewModel {
constructor(name?: string, subGroups?: ReportGroupViewModel[]) {
this.name = ko.observable(name || '');
this.subGroups = <KnockoutObservableArray<ReportGroupViewModel>>(ko.observableArray(subGroups || []));
}

name: KnockoutObservable<string>;
subGroups: KnockoutObservableArray<ReportGroupViewModel>;

addSubGroup(): void {
this.subGroups.push(new ReportGroupViewModel());
}

deleteSubGroup(subGroup: ReportGroupViewModel): void {
console.log(this.name());
this.subGroups.remove(subGroup);
}
}


In my view, I am using a recursive template to list the groups.

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


The simplified template is as follows:

<script id="subGroupTemplate" type="text/html">

<span class="item-actions pull-right">
<span data-bind="click: addSubGroup">Add Sub Group</span>
<span data-bind="click: $parent.deleteSubGroup">Delete Sub Group</span>
</span>

<li>
<input type="text" data-bind="textInput: name" placeholder="Name"/>
</li>

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

</script>


The problem I have is that the deletion binding does not fire on the parent object, but on the child object (i.e. it acts as if the binding were
data-bind="click: deleteSubGroup"
, without the
$parent.
) as can be seen from the name which is logged out to the JS console. I've created a jsfiddle at https://jsfiddle.net/po173qqh/3/

What am I doing wrong which is resulting in the deleteSubGroup method being called at the wrong level of the hierarchy?

Answer

By inputting $parent.deleteSubGroup you reference the method in $parent. The method is still called with the $data as this though. You'll have to tell knockout it has to use $parent as this by binding:

<span data-bind="click: $parent.deleteSubGroup.bind($parent, $data)">Delete Sub Group</span>

Alternatively, you could use the var self = this pattern and use self inside your method, but I'm not sure how this works in Typescript...

Edit: It might be the fiddle typescript conversion, but I had to remove the {} inside applyBindings to fix it: https://jsfiddle.net/qf66w75p/

Edit 2: As Tsvetomir Nikolov suggested in the comments, you can also explicitly bind the function to the parent context by using an arrow function. Definitely the cleanest option. Thanks, Tsvetomir! https://jsfiddle.net/y9ax95fy/