I'm trying to create a pattern for event handling in a class and it involved a function that quickly sets up an event for a class. It takes a function that can take any number of arguments and return void and a key to an object that has arrays of these functions. I'd like to use a generic to ensure the arguments of the function passed is the same as the function type in the array grabbed using the key. So if the T that is passed is () => void, then this.eventListeners[key] should also be of type T.
Perhaps this is impossible with Typescript as it is. In C++ I would pass the key through the template to ensure type safety, can this be done in Typescript?
type TonActionListener = (_:Action) => void;
type TonDisconnectListener = () => void;
type IeventListeners = {
onAction: Array<TonActionListener>;
onDisconnect: Array<TonDisconnectListener>;
}
export default class ControllerConnection {
protected eventListeners = {
onAction: [],
onDisconnect: [],
} as IeventListeners;
// add an event listener by key and return a callback to remove that event listener.
protected addEventListener<T>(key: (keyof IeventListeners), eventListener:T): (() => void) {
(this.eventListeners[key] as Array<T>).push(eventListener);
return () => void ((this.eventListeners[key] as Array<T>) = (this.eventListeners[key] as Array<T>).filter((l:T):boolean => l != eventListener));
}
onAction:any = (onActionListener: ((_:Action) => void)) =>
this.addEventListener('onAction', onActionListener);
onDisconnect:any = (onDisconnectListener: (() => void)) =>
this.addEventListener('onDisconnect', onDisconnectListener);
}
EDIT: So judging by this github issue, Typescript is turing complete, so there is definitely a way to achieve what i want. We just need to figure out how. https://github.com/Microsoft/TypeScript/issues/14833
EDIT: I've gotten far enough to create the necessary types, but I'm not sure how to write the function:
type IListeners = {
onAction: Array<(_: string) => void>;
onDisconnect: Array<() => void>;
}
type TKeyed<T extends keyof IListeners> = IListeners[T];
type TEventListener<Tk extends keyof IListeners> = <Tl extends TKeyed<Tk>>(listener: Tl) => void;
// "Tk is not defined", but I want the caller of addListener to define Tk
const addListener:TEventListener<Tk> = listener:Tk => {
// add listener to array here. Not important for this question.
}
const onAction: TEventListener<"onAction"> = addListener(str => void str + 'hello');
EDIT: I'm closer... I just need to get addListener to accept that Tk well be passed by the caller. Instead of being defined right away. I updated the above piece of code.