How to use another class method and the this context in a type method? How to use another class method and the this context in a type method? typescript typescript

How to use another class method and the this context in a type method?


This is more of a javascript than a typescript question.

In example 1 you are trying to use a "class pattern", in example 2 you use something like a "closure class" (theres a name for that pattern which I don't remember).

Both patterns are writeable in TS, and I personally prefer to keep "closure class" (example 2). So, you can just keep your code and add type annotations. Turn on strict: true and give type annotations to whatever the compiler yells you "has an implicit any type".

personal opinion ahead

Pattern nr.2 is usually more maintanable (sources?), pattern 1 is harder to refactor, requires more type annotations, more thinking and gives you room for the this binding issue. You'd still want to use pattern 1 on performance intensive things (ex: a game, which seems not to be your case), other than that, pattern 2 is your go. Even classic OOP cases (extend a class and override a method) are easily obtainable through option 2 (option bag pattern).

Typescript's type system is structural -- quite more powerful than "classic" java/C# -- and classes are not nominal in TS. Those are 2 more reasons not to use classes. class A {} and class B {} or any object are assignable if they have the same properties.

EDIT: About the this binding issue

If you really want to stick to ehh... classes...

  • You can't have your this be 2 things at the same time. If your this is your class, then you can find your element through event.target. If your this was rebound to an element... Oh, well.

  • So you'll have to call something like element.addEventListener("click", Instance.doSomething.bind(this)). addEventListener rebinds your function's this. .bind says: no.

  • Or element.addEventListener("click", (...i) => Instance.doSomething(...i))

  • If your method is really meant to be called from another this context, then write something like

    method(this: HTMLInputElement, x: number, y: string) { }

this is nothing more than sort of a hidden function parameter (python and lua explicitly send this as a 1st parameter, for instance), which is overriden by the onX calls, which is one of the JS billion-dollar problems, which is one of the reasons why JS classes suck.


Not entirely sure I have understood your problem correctly but I think you are saying the event handler is referencing the wrong object.

You need to save your context/scope outside of the event, then you can reference it inside, like this

class GroupItemMetadataProvider1{    function init(grid)    {        let context = this;        _grid = grid;        _grid.addEventListener('click', (e, args) => context.handleGridClick(e, args));        _grid.onKeyDown.subscribe(handleGridKeyDown);    }    function handleGridClick(e, args)    {        console.log(this); // will reference GroupItemMetadataProvider1    }}


Use can arrow function that captures this from the declaration context, and pass in the event context as an argument using a helper function. The helper function will take the event to subscribe to and will push the subscription in an array to make is simple to unsubscribe from all events.

subscriptions: Array<{ unsubscribe: () => any; }> = []bindAndSubscribe<TArg1, TArg2>(target: {    subscribe(fn: (e: TArg1, data: TArg2) => any)    unsubscribe(fn: (e: TArg1, data: TArg2) => any)}, handler: (context: any, e: TArg1, arg: TArg2) => void) {    let fn = function (e: TArg1, arg: TArg2) { handler(this, e, arg) };    target.subscribe(fn);    this.subscriptions.push({        unsubscribe: () => target.unsubscribe(fn)    });}protected init(grid: Slick.Grid<any>){    this._grid = grid;    // note paramters a and e are inffred correctly, if you ever want to add types    this.bindAndSubscribe(this._grid.onClick, (c, e, a)=> this.handleGridClick(c, e, a));    this.bindAndSubscribe(this._grid.onKeyDown, (c,e, a) => this.handleGridKeyDown(c,e));}protected destroy(){    if (this._grid)    {        this.subscriptions.forEach(s=> s.unsubscribe());    }}protected handleGridClick(context, e, args){     // correct this    this.m_options.toggleCssClass    //...}protected handleGridKeyDown(context, e){    // Correct this, context is a parameter    //...}

You can also declare the handlers directly as arrow functions, either approach will work:

protected init(grid: Slick.Grid<any>){    this._grid = grid;    // Arrow function not needed here anymore, the handlers capture this themselves.    this.bindAndSubscribe(this._grid.onClick, this.handleGridClick);    this.bindAndSubscribe(this._grid.onKeyDown, this.handleGridKeyDown);}protected handleGridClick = (context, e, args) => {     // correct this    this.m_options.toggleCssClass    //...}protected handleGridKeyDown = (context, e) => {    // Correct this, context is a parameter    //...}