5

I have these two Typescript classes:

class Base {
  value: string;

  lambdaExample = () => {
    this.value = 'one';
  }

  methodExample() {
    this.value = 'two';
  }
}

class Child extends Base {
  lambdaExample = () => {
    super.lambdaExample(); // Error, because I've overwritten (instead of overridden) the method
    this.value = 'three'; // ok
  }

  methodExample() => {
    super.methodExample(); // ok
    this.value = 'four'; // Error: this refers to window, not to the actual this
  }
}

How do I write my methods in such a way that this references are reliable, and I can override methods and call them from the parent class?

3
  • Possible duplicate of Call an overridden method from super class in typescript Commented Apr 28, 2016 at 14:08
  • 3
    It's not a duplicate: That question is about calling a class method in the constructor, not about overridden methods using class properties. Commented Apr 28, 2016 at 14:13
  • 1
    @smnbbrv That question has nothing to do with this one. Commented Apr 28, 2016 at 15:13

4 Answers 4

4

There's actually a good look at the different ways of tackling this problem on Microsoft's Git Wiki. It essentially comes down to this:

  • Bind or wrap the method every time it's called from a different context if you care about inheritance.
  • Turn the method into a property that contains a function if you don't.

There are many more stipulations in the actual Wiki and I really recommend you read the whole thing.

EDIT

An example of wrapping:

Given the class

class SomeClass {
    public someProp: string = "Hello World";

    public someMethod() {
        console.log(this.someProp);
    }

}

If you were to call someMethod from (for example) a click handler - someEl.onclick = instanceOfMyClass.someMethod; - an exception would be raised (assuming window doesn't have a property someProp).

You can circumvent this by either binding the function to instanceOfMyClass (not type-safe, not ES6 compatible) or by manually wrapping it (effectively what bind is doing, anyway):

someEl.onclick = function() {
    someInstanceOfMyClass.someMethod();
};

It's a little bit verbose and pedantic, but by calling someMethod as a property of someInstanceOfMyClass and not passing it into an event handler (which turns it into a property of window) you ensure that this is always an instance of MyClass.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm slightly unclear on the wrapping method. Would something like this work? class Child { methodExample() { var wrap = () => { super.methodExample() }; wrap(); }
So the wrapping is similar to bind in that you'd do it every time you called the function from a different context. For example, given class class MyClass { public someMethod() { console.log(this); } } to get this to be an instance of MyClass from a different context, you could call it like this: (() => { instanceOfMyClass.someMethod() })(). The important thing is that we called the method as a property of instanceOfMyClass rather than passing it directly (thus changing its context).
2

Your assumptions about the reasons for the errors are wrong, which I think is the cause of your problem...at least as I understand it.

lambdaExample = () => {
  this.value = 'one';
}

This line, for example is defining a property, not a method on Base, and you can't override a property. The only instance method you've defined in Base is methodExample.

In Child, you're assigning a new variable to lambaExample. Your call to super.lambaExample() fails because that can only access methods via super(); accessing properties is done via this. methodExample in your Child class shows up as a syntax error for me.

Note that you can still call super from Child in the overwritten lambaExample property, but only on methods. This works:

lambdaExample = () => {
  super.methodExample(); // Success on super.<somemethod>()
  this.value = 'three';
}

I'm only aware of one way to declare an instance method in a class, and if you're consistent with that syntax, this works as you would expect:

class Base {
  value: string;

  lambdaExample() {
    this.value = 'one';
  }

  methodExample() {
    this.value = 'two';
  }
}

class Child extends Base {
  lambdaExample() {
    super.lambdaExample();
    this.value = 'three';
  }

  methodExample() {
    super.methodExample();
    this.value = 'four';
  }
}

2 Comments

I can follow your entire post up until the last sentence: this does not work as expected in the method example, as explained in the question. The super call works in that case, but the this.value reference is undefined.
@Jorn - It will always be undefined until you set it to a value, which is only done once lambdaExample or methodExample is called.
2

I've found a workaround, but it's ugly:

class Base {
  someFunction = () => {
    this.otherFunction();
  }
  protected otherFunction = () => {
    // actual implementation here
  }
}
class Child {
  someFunction = () => {
    this.otherFunction();
    // additional implementation here
  }
}

This way, you can call someFunction on any instance, and still access the original implementation using otherFunction.

4 Comments

Why the downvote? I already said it's ugly, but so far it's also the only working solution...
Some Stackers are pretty finicky about answering your own question. I think this is a fine self-answer, but someone might have taken exception.
Well, it wasn't me, even though I believe I found a much better alternative that results in clean code.
There's absolutely nothing wrong with this answer, it's literally an example of how the TypeScript documentation says you should do overrides. The only thing I could see someone saying is to use Super but that would ignore the derived implementation, so your approach is actually the way to go. Seems ugly but it really isn't.
2

The scoping issue of this can be tackled with a very simple class decorator, and you no longer need to use the ugly* arrow function syntax for methods -- or think about scoping issues ever again:

function BindMethods(target: any): any {
    var originalCtor = target;
    var newCtor: any = function (...args) {
        var c: any = function () {
            // Methods are defined on prototype.
            var prototype = Object.getPrototypeOf(this);

            // Bind methods.
            Object.keys(prototype).forEach(propertyKey => {
                if (typeof this[propertyKey] === "function") {
                    prototype[propertyKey] = this[propertyKey].bind(this);
                }
            });

            // Invoke original constructor.
            return originalCtor.apply(this, args);
        }
        c.prototype = originalCtor.prototype;
        return new c();
    }

    // Copy prototype so 'intanceof' still works.
    newCtor.prototype = originalCtor.prototype;

    // Overrides original constructor.
    return newCtor;
}

Using it is as simple as snapping it on a class (methodExample method was modified for demonstration purposes only):

@BindMethods
class Base {
    value: string;

    methodExample() {
        console.log(this.value);
    }
}

This will ensure the reference to this is always correct (even with inheritance):

var base = new Base();
base.value = "two";
base.methodExample(); // "two"
setTimeout(base.methodExample, 20); // "two"

Unfortunately, there is no way to bind methods one-by-one using method decorators, as an instance reference is needed. If that's a requirement, you could use a decorator factory to pass property keys of methods to bind.


*ugly when used for methods.

Comments