There are 3 classes each in their own module (see below). At the top level, there is one instance of the Universe class (singleton), which creates one instance of the Workspace class, which in turn creates one instance of the Atom class.
I want to define a callback on the Atom instance, but I want the callback to be defined in the Universe since the callback needs to have identifiers from the Universe in its closure.
I know that I need to use anonymous function expressions rather than arrow functions because otherwise this
will be bound to the creation context and not the invocation context, see here: https://www.w3schools.com/js/js_arrow_function.asp
However, I am having a hard time reconciling this weird pattern with Typescript. It complains that:
'this' implicitly has type 'any' because it does not have a type annotation.
An outer value of 'this' is shadowed by this container.
I'm essentially looking for a way to tell Typescript:
In the callback, 'this' will eventually refer to an object of type Atom when it is actually invoked, even though when now when it is created 'this' refers to the closure object of type Universe.
import { CustomEvent } from "events";
import { Workspace } from "workspace";
class Universe {
private workspace: Workspace;
constructor() { ... }
universeMethod(): void {
console.log("a method on the Universe class");
}
private buildWorkspace() {
const that = this; // the callback, when invoked on the Atom instance, must have a reference to this Universe instance
const actionOnEventCallback = function (ev: { payload: CustomEvent }) {
console.log(that); // the creation context, 'that' should refer to the Universe instance
that.universeMethod();
console.log(workspace); // the object created in this method on the Universe class that transports the callback defined here to eventual atom object
workspace.workspaceMethod();
console.log(this); // the invocation context, 'this' should refer to the Atom instance
this.atomMethod();
}
const workspace = new Workspace({ color: "red", passThroughCallback: actionOnEventCallback });
this.workspace = workspace;
}
...
}
import { EventCallback, CustomEvent } from "events";
import { Atom } from "atom";
export interface WorkspaceDef {
color: string;
passThroughCallback: EventCallback<{ payload: CustomEvent }>;
class Workspace {
private readonly passThroughCallback;
private atom: Atom;
constructor(def: WorkspaceDef) {
this.passThroughCallback = def.passThroughCallback;
...
}
workspaceMethod(): void {
console.log("a method on the Workspace class");
}
private buildAtom() {
const that = this;
this.atom = new Atom({ age: 10, actionOnEvent: this.passThroughCallback });
}
...
}
import { EventCallback, CustomEvent } from "events";
import { ListeningElement } from "elements";
export AtomDef {
age: number;
actionOnEvent: EventCallback<{ payload: CustomEvent }>;
}
class Atom {
private listener: ListeningElement; // listens for the event then fires the "hand me down" callback
constructor(def: AtomDef) {
this.listener = createListener(def.actionOnEvent);
}
atomMethod(): void {
console.log("a method on the Atom class");
}
createActor(callback: EventCallback<{ payload: CustomEvent }>) {
return actor = new ListeningElement({
events: {
onClick: callback,
}
});
}
...
}