TypeScript: Wrong 'this' context in class methods

Currently, the type system included in TypeScript does not allow proper typing of the this context. There are attempts to fix that (See #3694 for example), but these will take a while to ripe and until then I would like to suggest a “quick fix” for class methods, because the following pitfall appears often when interacting with web-libraries. Consider the following code:

class Foo {
    private bar: string = "Bar";

    logBar(): void {
        console.log("Bar's value is: " + this.bar);
    }
}

// many javascript frameworks rebind the this context for callbacks,
// see for example jQuery's $("foo").click or React's onClick
function fireCallback(cb: (() => any)): void {
    let someObj = {
        hello: "42"
    };
    cb.call(someObj);
}

let x = new Foo();
fireCallback(x.logBar);

This code typechecks perfectly:

$ tsc --version
message TS6029: Version 1.5.0-beta
$ tsc --noImplicitAny main.ts

But does not produce the desired result:

$ node main.js
Bar's value is: undefined

Many libraries rebind the this context for callbacks, so my suggestion would be that the tsc transforms methods passed as arguments by wrapping them in an anonymous function like so:

fireCallback((...args: any[]) => x.logBar.call(x, args));

This should be okay, be cause inside a method the compiler assumes that this is of the classes type so there’s no way to interact with later bound this objects anyhow.

About this issue

  • Original URL
  • State: closed
  • Created 9 years ago
  • Comments: 16 (6 by maintainers)

Most upvoted comments

@kitsonk Hm, if that example is valid TypeScript I find that odd… Wouldn’t that mean that x have different types before and after x.logBar = undefined?

@RyanCavanaugh While I don’t know too much about the ES6 Spec, I found 4.3.31 method stating:

When a function is called as a method of an object, the object is 
passed to the function as its this value.

I agree that my first proposal is very hacky, so we came up with another solution together with @skogsbaer : How about compiling class methods to something like this:

class Foo {
    private bar: string = "Bar";

    logBar = () => {
        console.log("Bar's value is: " + this.bar);
    }
}

That way the typing of this is correct inside a class method.

  • Maybe, but in the current state the type checker indicates my program is sound, but it is not.
  • You can not use a different this in your class method due to the type checker? (Inside the method this has the type of the class?)
  • x.logBar can not be null or undefined iff x.logBar is a class method of x? I would expect the type system to catch this if this is possible
  • I would expect very few class methods to not need this is practice.

For this issue, I would suggest binding the method to “this” in the constructor. This works “this.showMessage = this.showMessage.bind(this);” .

Some nice info about it here: https://daveceddia.com/avoid-bind-when-passing-props/


class foo
{
    private m_Length: number = 0.0;
    private m_Width: number = 0.0;
    private m_Height: number = 0.0;

    constructor(x, y, z) { 
        this.m_Length = x;
        this.m_Width = y;
        this.m_Height = z;

        this.showMessage = this.showMessage.bind(this);
        this.calculateVolume = this.calculateVolume.bind(this);
    }

   public showMessage(callback) { 
        console.log(callback());
    }

   get volume():number {
        return this.calculateVolume();
    }


    public calculateVolume():number {
        return (this.m_Length * this.m_Width * this.m_Height);
    }

}

var x = new foo();
x.showMessage(x.calculateVolume);

@RyanCavanaugh So what’s a proposed practice to overcome the issue?