5

I have a simple TypeScript class with a private function that should get called, when the user clicks a button. The click event is bound via jQuery click() event in the constructor

HTML

<div id="foobar">
  <h2>Foo</h2>
  <button type="button">Bar</button>
</div>

TS

$(() => {
    var foo = new Bar($("#foobar"));
})

class Bar {
    private view: JQuery;
    private button: JQuery;

    constructor(view: JQuery) {
            // Fields
        this.view = view;
        this.button = view.find("button");

        // Events
        this.button.click(() => { this.buttonClick() });
    }

    private buttonClick() {
        this.view.find("h2").css("background-color", "red");
    }
}

https://jsfiddle.net/z4vo5u5d/18781/

But somehow, when executing the script, the console complains that buttonClick is not a function. What am I missing here?

I suppose it's a problem with "this" in TypeScript. But I can't figure out why.

Edited: as @Amadan mentioned:

this.button.click(() => { this.buttonClick() });

is translated incorrectly by jsfiddle into

this.button.click(function () { this.buttonClick(); });

Meanwhile, the compiler at typescriptlang.org/play translates it correctly as:

var _this = this;
...
this.button.click(function () { _this.buttonClick(); });
Buggy
  • 3,539
  • 1
  • 21
  • 38
Sandro
  • 2,998
  • 2
  • 25
  • 51
  • I don't know, it looks like a bug. I compiled your class at http://www.typescriptlang.org/play/, then pasted the JavaScript version over your TypeScript class, and it works as expected. Not sure what's going on. – Amadan Sep 05 '18 at 11:40
  • JSFiddle transpiles your code to `this.button.click(function () { this.buttonClick(); });`. It totally is not TS-related. – zerkms Sep 05 '18 at 11:53
  • @zerkms: I'd say that's very much TS-related - silently changing an arrow function into a non-arrow function means the TS transpiler that JSFiddle uses did a number two on OP's code. (I already demonstrated a non-buggy transpiler doesn't do that.) – Amadan Sep 05 '18 at 11:57
  • 1
    @Amadan TS does not transpile arrow functions into that, TS transpiles it correctly. Whatever jsfiddle uses produces the wrong output, it's not a TS compiler. – zerkms Sep 05 '18 at 11:58
  • 2
    @zerkms: I think we actually agree, just in non-agreeing terms. TS can't transpile correctly or incorrectly, it's a language. TS _transpiler_ that JSFiddle uses is non-compliant. TS transpiler that one uses in Node, or the one on the playground, does it correctly. You can't say it's not a TS transpiler, any more than you can say an apple with a worm is not an apple. – Amadan Sep 05 '18 at 12:01
  • 2
    Right, what I probably meant is that - it's not tsc or TS language problem, it's jsfiddle problem and should be reported there. – zerkms Sep 05 '18 at 12:02
  • 2
    Submitted at https://github.com/jsfiddle/jsfiddle-issues/issues/1362 – Amadan Sep 05 '18 at 12:11
  • Response from the team: "The TypeScript transpiler is outdated, until we change the way transpilers are working in JSFiddle we have to stick with the outdated one since the new one doesn't work in our setup." – Amadan Sep 21 '18 at 02:38

1 Answers1

-1

I think what's going wrong here is what I like to call a scoping issue.

In old versions of TypeScript, like the one JSFiddle uses, the scope of this is not the same everywhere you use it. It's a dynamic variable that changes depending on where it's called. The this.buttonClick() in the button's event function evaluates to the buttonClick() function of the button itself, which it doesn't have, since it belongs to Bar.

Try assigning a const self = this; value just below your constructor(view: JQuery) { and replace all occurrences of this with self in your constructor function.

This makes sure that the object that self evaluates to is always the object itself, not the current context, which might not be what you're aiming at.

class Bar {
    private view: JQuery;
    private button: JQuery;

    constructor(view: JQuery) {
        const self = this;

        // Fields
        self.view = view;
        self.button = view.find("button");

        // Events
        self.button.click(() => { self.buttonClick() });
    }

    private buttonClick() {
        this.view.find("h2").css("background-color", "red");
    }
}

That should work perfectly. I've ran into this problem many times before. I've gotten used to declaring const self = this; in every function I write.

Edited: as @Amadan mentioned:

this.button.click(() => { this.buttonClick() });

is translated incorrectly by jsfiddle into

this.button.click(function () { this.buttonClick(); });

Meanwhile, the compiler at typescriptlang.org/play translates it correctly as:

var _this = this;
...
this.button.click(function () { _this.buttonClick(); });
Buggy
  • 3,539
  • 1
  • 21
  • 38
Rick
  • 1,172
  • 11
  • 25
  • JS arrow functions are lexically scoped, no need to create `self` variable, `this` is **guaranteed** to have the same value. – zerkms Sep 06 '18 at 08:09
  • @zerkms As the question states, this is obviously not guaranteed with every version of TypeScript out there. This answer is a valid solution to OP's problem with whatever they are using, JSFiddle (which, to my knowledge, uses TS 1.7.3(!), by the way) or otherwise. Saying "tell JSFiddle to update their stuff" is **NOT** a solution. – Rick Sep 06 '18 at 08:29
  • 1
    @RickvanOsta If a widely-used website is using a version of a tool with a bug that makes said tool _non-compliant_, then "telling them to update their stuff" is _the_ solution. – Patrick Roberts Sep 06 '18 at 08:35
  • @RickvanOsta it is guaranteed by the TS spec. – zerkms Sep 06 '18 at 08:51
  • 1
    "I've gotten used to declaring const self = this; in every function I write" --- it makes very little sense to do so: if you don't trust a compiler, how do you trust `2+2` equals `4`? Where is that line? – zerkms Sep 06 '18 at 08:52
  • 1
    @zerkms I meant functions I write in JSFiddle, not in my own projects. I'm not a barbarian. – Rick Sep 06 '18 at 09:17
  • @PatrickRoberts So work-arounds aren't solutions? I'm not denying that JSFiddle should update their transpiler to a working version, I'm saying that if you want to make it work using JSFiddle *right now*, here's how. – Rick Sep 06 '18 at 09:21