Code:
export class ViewModel {
public users: knockout.koObservableArrayBase;
constructor () {
this.users = ko.observableArray([]);
this.removeUser = this.removeUser.bind(this);//<-- Here compiller shows error
}
removeUser(user: User): void {
this.users.remove(user);
}
}
Html:
<table>
<thead>
<tr>
<th>Name</th>
<th>Surname</th>
</tr>
</thead>
<tbody data-bind="foreach: users">
<tr>
<td><a href="#" data-bind="click: $root.removeUser">Remove</a></td>
<td data-bind="text: name"></td>
<td data-bind="text: surname"></td>
</tr>
</tbody>
</table>
Problem is in removeUser method. By default, if I don't bind context, this == UserToDelete - not viewModel object. If I adding to constructor: this.removeUser = this.removeUser.bind(this); (manually enforce context)
, then context is as needed this == viewmodel, but then TypeScript complains for "Cannot convert Function to (user:User)=>void requires a call signatures, but Function lacks one."
I'm not familiar with ko so perhaps there is a better way to solve the context switching, but your typescript compiler error is caused by 'bind' returning type 'Function' which is incompatible with the type of 'removeUser'. You should be able to solve this by casting the returned function into the original type signature as follows:
this.removeUser = <(user: User) => void> this.removeUser.bind(this);
Well i had the same problem thats why i came up with the following base class to fix my issue
export class ViewModelBase {
private prefix: string = 'On';
public Initialize() {
for (var methodName in this) {
var fn = this[methodName];
var newMethodName = methodName.substr(this.prefix.length);
if (typeof fn === 'function' && methodName.indexOf(this.prefix) == 0 && this[newMethodName] == undefined) {
this[newMethodName] = $.proxy(fn, this);
}
}
}
}
what this does is loop all members of your class and if a method starts with On it will create a new method without On that will call the original method with the right context.
Not that $.proxy
is a jquery call so jquery is needed for this to work.
Well, the simplest solution and what I normally do with typescript and knockout js is that I don't declare functions called from knockout on the prototype but at the constructor. So, I would make it like this:
export class ViewModel {
public users: knockout.koObservableArrayBase;
removeUser:(user: User) => void;
constructor () {
this.users = ko.observableArray([]);
this.removeUser = (user:User) => {
this.users.remove(user);
}
}
}
An alternative is to change the click binding to use JavaScript's bind function to force the value of this
to be your view model: data-bind="click: $root.MyFunc.bind($root)"
.
Note that $data
and the click event
object will still be passed in as arguments to MyFunc
from Knockout as described by the click binding specification. If you need to override the arguments being passed to MyFunc
, simply pass them into the bind function after $root
like so: .bind($root, param1, param2)
. Technically, these arguments will be prepended to the arguments being supplied by Knockout, giving arguments [param1, param2, data, event]
.
I ran into the same problem. To get the right context you can use the parameters that are passed by the clickbinding. The clickbinding passes 2 parameters, ie the user and the jquery event of the click.
If you take the jquery event, and than use the ko.contextFor() function, you can obtain the right context.
Your function would look something like:
removeUser(user: User, clickEvent: any): void {
var self = ko.contextFor(clickEvent.srcElement).$root;
self.users.remove(user);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With