2

I am having issues with the binding context using ES6 classes.

here is a jsfiddle to explain.

when i declare a click function on the class like

class viewModel {
    constructor() {
        this.data = ko.observableArray([{ firstName: "phil" }, { firstName: "person" }]);
        this.selectedPerson = ko.observable("none selected");
    }
    selectUser(data){
        console.log(this);
        this.selectedPerson(data.firstName);
    }
}

I have to provide a binding in the markup like so: <div data-bind="text: firstName, click: $parent.selectUser.bind($parent)">

but when i declare the click in the constructor I don't have to provide the context.

anybody know why?

Matthisk
  • 1,383
  • 9
  • 18
  • I suggest using https://babeljs.io/docs/plugins/transform-class-properties/. WIth this u can write a method as an arrow function – Adam Wolski Jan 20 '17 at 14:35

1 Answers1

1

Knockout's event binding (which click uses under the hood) does three things:

  • Pass the current binding context's $data,
  • Pass the event
  • apply the event listener bound to the current $data

(Source)

This means that when you tap on a user, this is what happens:

viewModelInstance.selectUser.apply(user, [user, event]);

If you want to refer to this in your handler, it's important to know the differences between arrow functions, prototype methods and "unbound" property functions:

this.doSomething = function () { /* ... */ };
this.doSomething = () => { /* ... */ };

MyClass.prototype.doSomething = function() { /* ... */ };

When you write:

this.selectUser = (data) => { /* ... */ };

You essentially do:

this.selectUser = function(data) { /* ... */ }.bind(this);

This means the viewModel instance is "fixed"/bound to the method. Defining it outside the constructor makes it a prototype method that can be bound to any this context by other code.

Search for "es6 arrow functions" and this to find some more answers on how this works. For example: When should I use Arrow functions in ECMAScript 6?

Community
  • 1
  • 1
user3297291
  • 19,011
  • 1
  • 24
  • 39
  • so to avoid having to call a .bind($parent/$/root) i can declare it in the constructor to make "this" bind correctly ? i though we want to avoid logic in the constructor or does that only apply to typed languages ? – Philip Sankey Jan 20 '17 at 13:36
  • Defining it in the constructor has its downsides (like: every instance redefines the method) but it will work. Alternatively, you could include a reference to the selection model in each user, and make them have their own `select` method (my personal preference). A third option would be to use a ["postbox pattern"](https://github.com/rniemeyer/knockout-postbox) to communicate between models. (seems like overkill for this straight forward example) – user3297291 Jan 20 '17 at 13:45
  • i like the second one too(if we are on the same page so https://jsfiddle.net/mbr4mjtp/1/ for ref) but how would this work with dynamic data from a server? we would need to loop over each and add a select function. or use the mapping util. or am i totally missing the mark ? – Philip Sankey Jan 20 '17 at 14:35
  • Not missing the mark; that was exactly what I meant. If you're working with plain objects from the server, I'd advice you to make a view model with a constructor that handles both the raw data and the selection logic. For example: https://jsfiddle.net/u8wngza3/ Another tip: you could put the "name extraction logic" in a computed, which would allow you to just pass the observable that stores the selection along instead of a setter function: https://jsfiddle.net/jwupjy0m/ – user3297291 Jan 20 '17 at 14:51