35

I've been creating a htmlHelper function using TypeScript and KnockoutJS to edit a list of emails.

The list of emails is a Knockout ObservableArray called emails, and I have a link against each item to delete them. This is the HTML fragment:

<ul data-bind="foreach: emails" >
    <li>
        <a href="#" data-bind="click: $parent.deleteItem">Delete</a>
        &nbsp;<span data-bind="text: $data"></span>
    </li>
</ul>

The delete link is bound to $parent.deleteItem this is a method in the viewmodel:

// remove item
public deleteItem(emailToDelete: string) {
    // remove item from list
    this.emails.remove(emailToDelete);
}

This all works until the deleteItem method is executed. The "this" in this method when it is called is the item in the array, and not the view model. Hence this.emails is a null reference and fails.

I know that TypeScript supports the Lambda syntax but I can't find the right way to write this (there few examples out there).

Or is there a different approach I could take?

Joakim Johansson
  • 3,196
  • 1
  • 27
  • 43
Quango
  • 12,338
  • 6
  • 48
  • 83
  • I haven't used TypeScript, but the usual solution to this in Javascript is to save the viewmodel instance reference to a variable, e.g. `var self = this`, then use it to refer to the `emails` object, e.g. `self.emails.remove(emailToDelete)`. Maybe you know ts syntax that does that. – Jensen Ching Nov 08 '12 at 03:00
  • Follow up question: http://stackoverflow.com/questions/17877103/typescript-knockout-best-way-to-retain-this – Rune Jeppesen Jul 26 '13 at 08:59

8 Answers8

49

You can get correct closure for 'this' by declaring method body inside class constructor

class VM {
    public deleteItem: (emailToDelete: string) => void;

    constructor() {
        this.deleteItem = (emailToDelete: string) => {
            // 'this' will be pointing to 'this' from constructor
            // no matter from where this method will be called
            this.emails.remove(emailToDelete);
        }
    }        
}

UPDATE:

It seems that since Typescript ver 0.9.1 you can achieve the same result by using lambda field initializers:

class VM {
    public deleteItem = (emailToDelete: string) => {
        this.emails.remove(emailToDelete);
    }        
}
Slawek
  • 2,592
  • 1
  • 24
  • 26
  • Thanks for this: it's only this method that needs this fix, as all the others seem to work. – Quango Nov 08 '12 at 21:44
  • note also that you have to write `this.deleteItem = (emailToDelete: string) => {...}`. You can not use `this.deleteItem = function(emailToDelete: string) {...}` syntax as `this` here means the context of inner function. – gerichhome Dec 24 '13 at 07:55
  • 1
    note that this won't create a "prototype.function". This is not so good out of a performance-perspective – Richard Apr 22 '14 at 14:29
  • 3
    @Richard that will only really become a problem if OP is going to have hundreds of instances of this object, there will likely never be more than a single view model so it should not be a concern. Never worry about performance until you have to worry about performance :) – Scott Simontis Jul 05 '15 at 08:51
  • This one did well for me – Kent Aguilar Nov 15 '16 at 06:42
26

Gloves people! Just bind $parent as this:

<a href="#" data-bind="click: $parent.deleteItem.bind($parent)">Delete</a>
RPJ
  • 261
  • 3
  • 2
6
declare class Email { }
declare class ObservableArray {
    remove(any): void;
}

class MyViewModel {
    public emails : ObservableArray;

    constructor() {
        Rebind(this);
    }

    public deleteItem(emailToDelete: Email) {
        this.emails.remove(emailToDelete);
    }
}

function Rebind(obj : any)
{
    var prototype = <Object>obj.constructor.prototype;
    for (var name in prototype) {
        if (!obj.hasOwnProperty(name)
                && typeof prototype[name] === "function") {
            var method = <Function>prototype[name];
            obj[name] = method.bind(obj);
        }
    }
}

You might want a polyfill for Function.bind():

// Polyfill for Function.bind(). Slightly modified version of
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (typeof Function.prototype.bind !== "function") {
    Function.prototype.bind = function(oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = <any[]> Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function() {},
            fBound = function() {
                return fToBind.apply(this instanceof fNOP && oThis ? this: oThis, aArgs.concat());
            };

        fNOP.prototype = this.prototype;
        fBound.prototype = new fNOP();

        return fBound;
    };
}
Markus Jarderot
  • 86,735
  • 21
  • 136
  • 138
6

My final solution is a base class, that rebinds all prototype functions to itself on constructor. Much like Markus Jarderot's solution.

class BaseClass {
    constructor() {
        for (var i in this) {
            if (!this.hasOwnProperty(i) && typeof (this[i]) === 'function' && i != 'constructor') {
                this[i] = this[i].bind(this);
            }
        }
    }
}

Advantages:

  • All subclasses are forced to call super constructor, which is the behavior I wanted.
  • When the rebind code is executed, there are only prototype functions in the object (variables are added later).
  • It avoids the creation of big functions on every object. Only a small proxy function is created per object when you call bind.
  • Better organization of class code by not putting functions on constructor.
  • Any function can be used as a callback, you don't need to change the code when a function is called from an event.
  • You don't have the risk of binding functions twice.
  • It is better to bind the function only once, instead doing it in the view every time the click/event binding is executed.

PS:
You still will need the bind polyfill.
I'm using typesript 0.9.5

Lucas Lorentz
  • 61
  • 2
  • 4
2

to add my 2 cents, there's also a dirty way, that leverages the variable _this created by the Typescript compiler to keep a reference on this :

public deleteItem(emailToDelete: string) {
    var that = eval('_this');
    // remove item from list
    that.emails.remove(emailToDelete); // remove? in JS,  really? 
}
pierroz
  • 7,653
  • 9
  • 48
  • 60
1

I was inspired by the bind answer and came up with this, I think it a little easier to read.

<a href="#" data-bind="click: function () {$parent.deleteItem()}">Delete</a>

Wrap the method in a lambda/anonymous function. Don't forget the ().

ctrl-alt-delor
  • 7,506
  • 5
  • 40
  • 52
1

Use data-bind something like this:

data-bind="click:$parent.deleteItem.bind($parent)"

Assign this to that as shown below

public deleteItem(itemToDelete) 
{
    var that = this;
    // remove item from list
    that.emails.remove(itemToDelete); 
}
SnareChops
  • 13,175
  • 9
  • 69
  • 91
0

Although I prefer Markus' solution, here's what I have used before to work around this issue:

public fixThis(_this, func) {
    return function () {
        return _this[func].apply(_this, arguments);
    };
}

<a href="#" data-bind="click: fixThis($parent, 'deleteItem')">Delete</a>

Note that additional arguments can be passed to the method by adding them after the name of the method:

fixThis($parent, 'deleteItem', arg1, arg2);
Xavier Poinas
  • 19,377
  • 14
  • 63
  • 95