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 yourthis
is your class, then you can find your element throughevent.target
. If yourthis
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'sthis
..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 likemethod(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 //...}