gadeynebram gadeynebram - 1 month ago 6
TypeScript Question

What is the difference in typescript between defining a method in the constructor or as a regular class method?

I'm migrating a angular 1 application from javascript to typescript. Actually everything is done but I can't get my head arround one specific case.

I have some directives that use a link function.

in pseudocode:

class SomeDirective implements ng.IDirective{
static $inject=['someService'];
public restrict: string='EA';
public scope: any;
constructor(private someService: SomeService){
this.scope={
data: '='
}
}
}


now I want to Define a link function like

public link(scope, ele, attrs){
do some action with this.someService.someField
}


If I define this method in the class then I get an error: property 'someField' not found on undefined.

If I define the method as follows in the constructor then it does work. What is the difference?

this.link=(scope,ele,attrs)=>{
do some action with this.someService.someField
}


link is defined here as a class property of type any.

Answer

Defining the function in the constructor creates, as you wrote, a class property, and it's assigned to the instance only.
A class method on the other hand is defined on the prototype:

class A {
    fn2: () => void;

    constructor() {
        this.fn2 = () => {
            console.log("A.fn2");
        };
    }

    fn1(): void {
        console.log("A.fn1");
    }
}

Compiles into:

var A = (function () {
    function A() {
        this.fn2 = function () {
            console.log("A.fn2");
        };
    }
    A.prototype.fn1 = function () {
        console.log("A.fn1");
    };
    return A;
}());

Using the instance function is problematic if you want to override the function in a derived class:

class B extends A {
    constructor() {
        super();

        this.fn2 = () => {
            super.fn2(); // Error: Only public and protected methods of the base class are accessible via the 'super' keyword
            console.log("B.fn2");
        };
    }

    fn1(): void {
        super.fn1();
        console.log("A.fn1");
    }
}

(code in playground)

You can save a reference for the previous this.fn2 before assigning the new function (in B) so that you'll have a super to call, but that isn't very comfortable.

The plus of course (if you're using arrow functions) is that the functions are bound to the right this out of the box.

It's up to you to decide what's more important to you, I prefer the methods way, and if I pass them as callbacks then I just bind them.


Edit

The right this should always be correct when you invoke the methods regularly:

class A {
    bound: () => void;

    constructor() {
        this.bound = () => {
            console.log("bound: ", this);
        };
    }

    unbound(): void {
        console.log("unbound: ", this);
    }
}

let a1 = new A();
a1.bound(); // A {}
a1.unbound(); // A {}

As expected, this here is the instance of A, but:

function later(fn: Function) {
    setTimeout(fn, 100);
}

later(a1.bound); // A {}
later(a1.unbound); // Window { ... }

(code in playground)

As you can see here, when invoking a1.unbound using a setTimeout (or if you pass it as a callback) then this isn't the instance of A.
That can be fixed like so:

later(a1.unbound.bind(a1)); // A {}