77

I've attempted to write a class in TypeScript that has a method defined which acts as an event handler callback to a jQuery event.

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin(onFocusIn);
    }

    onFocusIn(e: JQueryEventObject) {
        var height = this.textarea.css('height'); // <-- This is not good.
    }
}

Within the onFocusIn event handler, TypeScript sees 'this' as being the 'this' of the class. However, jQuery overrides the this reference and sets it to the DOM object associated with the event.

One alternative is to define a lambda within the constructor as the event handler, in which case TypeScript creates a sort of closure with a hidden _this alias.

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin((e) => {
            var height = this.textarea.css('height'); // <-- This is good.
        });
    }
}

My question is, is there another way to access the this reference within the method-based event handler using TypeScript, to overcome this jQuery behavior?

Todd
  • 12,995
  • 3
  • 30
  • 25

12 Answers12

104

The scope of this is preserved when using the arrow function syntax () => { ... } - here is an example taken from TypeScript For JavaScript Programmers.

var ScopeExample = { 
  text: "Text from outer function", 
  run: function() { 
    setTimeout( () => { 
      alert(this.text); 
    }, 1000); 
  } 
};

Note that this.text gives you Text from outer function because the arrow function syntax preserves the "lexical scope".

Fenton
  • 241,084
  • 71
  • 387
  • 401
  • The context of "this" isn't be modified with your example and so what you showed doesn't really apply. – Paul Mendoza Dec 04 '12 at 06:31
  • @PaulMendoza actually if you change it to `setTimeout( function () { alert(this.text); }, 1000);` you will get `undefined`. – Fenton Dec 04 '12 at 09:47
  • You're right. My bad. Somehow the ()=> tells the compiler that this means something different than doing function() { } – Paul Mendoza Dec 12 '12 at 04:09
  • 9
    This is wonderful. Props! What this does behind the scenes is create a `var _this = this` at the top of the method and then references `_this` in the anonymous function. – Richard Rout Jun 19 '13 at 02:14
23

So as stated there is no TypeScript mechanism for ensuring a method is always bound to its this pointer (and this isn't just a jQuery issue.) That doesn't mean there isn't a reasonably straightforward way to address this issue. What you need is to generate a proxy for your method that restores the this pointer before calling your callback. You then need to wrap your callback with that proxy before passing it into the event. jQuery has a built in mechanism for this called jQuery.proxy(). Here's an example of your above code using that method (notice the added $.proxy() call.)

class Editor { 
    textarea: JQuery; 

    constructor(public id: string) { 
        this.textarea = $(id); 
        this.textarea.focusin($.proxy(onFocusIn, this)); 
    } 

    onFocusIn(e: JQueryEventObject) { 
        var height = this.textarea.css('height'); // <-- This is not good. 
    } 
} 

That's a reasonable solution but I've personally found that developers often forget to include the proxy call so I've come up with an alternate TypeScript based solution to this problem. Using, the HasCallbacks class below all you need do is derive your class from HasCallbacks and then any methods prefixed with 'cb_' will have their this pointer permanently bound. You simply can't call that method with a different this pointer which in most cases is preferable. Either mechanism works so its just whichever you find easier to use.

class HasCallbacks {
    constructor() {
        var _this = this, _constructor = (<any>this).constructor;
        if (!_constructor.__cb__) {
            _constructor.__cb__ = {};
            for (var m in this) {
                var fn = this[m];
                if (typeof fn === 'function' && m.indexOf('cb_') == 0) {
                    _constructor.__cb__[m] = fn;                    
                }
            }
        }
        for (var m in _constructor.__cb__) {
            (function (m, fn) {
                _this[m] = function () {
                    return fn.apply(_this, Array.prototype.slice.call(arguments));                      
                };
            })(m, _constructor.__cb__[m]);
        }
    }
}

class Foo extends HasCallbacks  {
    private label = 'test';

    constructor() {
        super();

    }

    public cb_Bar() {
        alert(this.label);
    }
}

var x = new Foo();
x.cb_Bar.call({});
SimonGoldstone
  • 5,096
  • 3
  • 27
  • 38
Steven Ickman
  • 1,936
  • 1
  • 12
  • 7
  • 1
    The `$.proxy()` call is a great solution for my situation. Having a `this` alias would involve complicating TS and, as others have pointed out, would be difficult to do in a backwards-compatible way. – Todd Oct 06 '12 at 18:45
  • 1
    Thought I'd add that I know the code for HasCallbacks looks a little scary but all its doing is going through your classes members and pre-wiring them with proxies. If your project is small it's probably best to use $.proxy(). If your project is large, however, the HasCallbacks class will result in less code downloaded to the client (you don't have all those extra $.proxy() calls) and will be less error prone. Execution wise both approaches have about the same performance (HasCallbacks has a one-time enumeration overhead for each class) so it's really your choice. – Steven Ickman Oct 06 '12 at 19:16
  • 3
    I think the your answer can be misleading since it gave me the impression that TypeScript didn't support this, whereas it actually does if you just define your methods using the arrow syntax. – Sam Dec 23 '13 at 04:22
  • This answer may provide a solution but is not correct since TypeScript does provide a mechanism for maintaining lexical scoping of this. See Sam's answers for 2 options of using arrow functions to solve this. – bingles May 24 '15 at 12:57
21

As covered by some of the other answers, using the arrow syntax to define a function causes references to this to always refer to the enclosing class.

So to answer your question, here are two simple workarounds.

Reference the method using the arrow syntax

constructor(public id: string) {
    this.textarea = $(id);
    this.textarea.focusin(e => this.onFocusIn(e));
}

Define the method using the arrow syntax

onFocusIn = (e: JQueryEventObject) => {
    var height = this.textarea.css('height');
}
Sam
  • 40,644
  • 36
  • 176
  • 219
6

You can bind a member function to its instance in the constructor.

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin(onFocusIn);
        this.onFocusIn = this.onFocusIn.bind(this); // <-- Bind to 'this' forever
    }

    onFocusIn(e: JQueryEventObject) {
        var height = this.textarea.css('height');   // <-- This is now fine
    }
}

Alternatively, just bind it when you add the handler.

        this.textarea.focusin(onFocusIn.bind(this));
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • this is my favorite method, but unfortunately you need a polyfill for `bind` for older browsers. – Antoine Mar 27 '15 at 10:22
4

Try this

class Editor 
{

    textarea: JQuery;
    constructor(public id: string) {
        this.textarea = $(id);
        this.textarea.focusin((e)=> { this.onFocusIn(e); });
    }

    onFocusIn(e: JQueryEventObject) {
        var height = this.textarea.css('height'); // <-- This will work
    }

}
Danny Beckett
  • 20,529
  • 24
  • 107
  • 134
Gabriel C.
  • 41
  • 2
3

Steven Ickman's solution is handy, but incomplete. Danny Becket and Sam's answers are shorter and more manual, and fail in the same general case of having a callback that needs both dynamic and lexically scoped "this" at the same time. Skip to my code if my explanation below is TL;DR...

I need to preserve "this" for dynamic scoping for use with library callbacks, and I need to have a "this" with lexical scoping to the class instance. I argue that it is most elegant to pass the instance into a callback generator, effectively letting the parameter closure over the class instance. The compiler tells you if you missed doing so. I use a convention of calling the lexically scoped parameter "outerThis", but "self" or another name might be better.

The use of the "this" keyword is stolen from the OO world, and when TypeScript adopted it (from ECMAScript 6 specs I presume), they conflated a lexically scoped concept and a dynamically scoped concept, whenever a method is called by a different entity. I'm a little miffed at this; I would prefer a "self" keyword in TypeScript so that I can hand the lexically scoped object instance off of it. Alternately, JS could be redefined to require an explicit first-position "caller" parameter when it is needed (and thus break all web pages in one fell swoop).

Here's my solution (excised from a large class). Take a gander in particular at the way the methods are called, and the body of "dragmoveLambda" in particular:

export class OntologyMappingOverview {

initGraph(){
...
// Using D3, have to provide a container of mouse-drag behavior functions
// to a force layout graph
this.nodeDragBehavior = d3.behavior.drag()
        .on("dragstart", this.dragstartLambda(this))
        .on("drag", this.dragmoveLambda(this))
        .on("dragend", this.dragendLambda(this));

...
}

dragmoveLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} {
    console.log("redefine this for dragmove");

    return function(d, i){
        console.log("dragmove");
        d.px += d3.event.dx;
        d.py += d3.event.dy;
        d.x += d3.event.dx;
        d.y += d3.event.dy; 

        // Referring to "this" in dynamic scoping context
        d3.select(this).attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });

        outerThis.vis.selectAll("line")
            .filter(function(e, i){ return e.source == d || e.target == d; })
            .attr("x1", function(e) { return e.source.x; })
            .attr("y1", function(e) { return e.source.y; })
            .attr("x2", function(e) { return e.target.x; })
            .attr("y2", function(e) { return e.target.y; });

    }
}

dragging: boolean  =false;
// *Call* these callback Lambda methods rather than passing directly to the callback caller.
 dragstartLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void} {
        console.log("redefine this for dragstart");

        return function(d, i) {
            console.log("dragstart");
            outerThis.dragging = true;

            outerThis.forceLayout.stop();
        }
    }

dragendLambda(outerThis: OntologyMappingOverview): {(d: any, i: number): void}  {
        console.log("redefine this for dragend");

        return function(d, i) {
            console.log("dragend");
            outerThis.dragging = false;
            d.fixed = true;
        }
    }

}
Eric
  • 663
  • 7
  • 18
2

TypeScript doesn't provide any extra way (beyond the regular JavaScript means) to get back to the 'real' this reference other than this remapping convenience provided in the fat arrow lambda syntax (which is allowable from a back-compat perspective since no existing JS code could be using a => expression).

You could post a suggestion to the CodePlex site, but from a language design perspective there's probably not much that can happen here, since any sane keyword the compiler could provide might already be in use by extant JavaScript code.

Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
2

I have faced a similar problem. I think you can use .each() in many cases to keep this as a different value for later events.

The JavaScript way:

$(':input').on('focus', function() {
  $(this).css('background-color', 'green');
}).on('blur', function() {
  $(this).css('background-color', 'transparent');
});

The TypeScript way:

$(':input').each((i, input) => {
  var $input = $(input);
  $input.on('focus', () => {
    $input.css('background-color', 'green');
  }).on('blur', () => {
    $input.css('background-color', 'transparent');
  });
});

I hope this helps someone.

obrys
  • 13
  • 2
2

You can use js eval function: var realThis = eval('this');

reptile
  • 175
  • 1
  • 9
0

You could store your reference to this in another variable.. self perhaps, and access the reference that way. I don't use typescript, but that's a method that's been successful for me with vanilla javascript in the past.

Sheridan Bulger
  • 1,214
  • 7
  • 9
  • 2
    The only issue with this solution is that the `this` reference within the callback method masks the `this` reference of the underlying object, so there's no way to access the new property via the correct `this`. – Todd Oct 06 '12 at 18:31
  • 1
    While this is a valid approach, I think it's better to use the built-in functionality provided by TypeScript to achieve this. – Sam Dec 23 '13 at 04:27
  • 2
    Todd: That's exactly why you stored the proper "this" in a variable before the new scope (and therefore the new "this") is entered. This is a pretty common javascript problem, not just typescript (@Sam). Not sure why the down-vote but it's all good. – Sheridan Bulger Jan 07 '14 at 17:30
  • @SheridanBulger, the down-vote is because this is a TypeScript question and TypeScript already provides its own way to achieve this. I think it's generally a good idea to make use of language features rather than reimplementing them yourself. – Sam Jan 07 '14 at 21:33
  • @SheridanBulger Downvoted... this is not a TypeScript answer. – BenjaminPaul Jan 13 '15 at 17:23
0

Check out this blog post http://lumpofcode.blogspot.com/2012/10/typescript-dart-google-web-toolkit-and.html, it has a detailed discussion of techniques for organizing calls within and across TypeScript classes.

Ezward
  • 17,327
  • 6
  • 24
  • 32
0

There is much simpler solution than all the above answers. Basically we fall back to JavaScript by using key word function instead of using '=>' construct which will translate the 'this' into class 'this'

class Editor {
    textarea: JQuery;

    constructor(public id: string) {
        var self = this;                      // <-- This is save the reference
        this.textarea = $(id);
        this.textarea.focusin(function() {   // <-- using  javascript function semantics here
             self.onFocusIn(this);          //  <-- 'this' is as same as in javascript
        });
    }

    onFocusIn(jqueryObject : JQuery) {
        var height = jqueryObject.css('height'); 
    }
}
Derek Liang
  • 1,142
  • 1
  • 15
  • 22