I use various Capacitor plugins in my Angular app.
They basically consists on methods, and also listeners. Here comes an example from Keyboard plugin :
addListener(eventName: 'keyboardWillShow', listenerFunc: (info: KeyboardInfo) => void): Promise<PluginListenerHandle> & PluginListenerHandle;
addListener(eventName: 'keyboardDidShow', listenerFunc: (info: KeyboardInfo) => void): Promise<PluginListenerHandle> & PluginListenerHandle;
addListener(eventName: 'keyboardWillHide', listenerFunc: () => void): Promise<PluginListenerHandle> & PluginListenerHandle;
addListener(eventName: 'keyboardDidHide', listenerFunc: () => void): Promise<PluginListenerHandle> & PluginListenerHandle;
I created a method that calls these addListener methods, but I don't know how to type it to achieve what I want :
myMethod(Keyboard, 'keyboardWillShow') // return type : Observable<KeyboardInfo>
myMethod(Keyboard, 'keyboardWillHide') // return type : Observable<void>
myMethod(Keyboard, 'nonexisting') // TS Error
I want to type my method in a way that it accepts plugin in first argument, and only accepts in second arg event names for which an addListener overload exists and accept it as its first arg. I also want, if possible, to infer the type of the callback argument according to the event name.
For now, I have no idea of how to achieve this.
type CapacitorPlugin = {
addListener: (eventName: any, listener: (event: any) => void) => Promise<PluginListenerHandle> & PluginListenerHandle
}
...
export function fromCapacitorListener(plugin: CapacitorPlugin, eventName: unknown): Observable<any> { /* ... */ }
Here is the typescript example demonstrating, multiple return types based on an input parameter.
First we define the events in a type.
type TypeName = "keyboardWillShow" | "keyboardDidShow" | "keyboardWillHide" | "keyboardDidHide";
Then we define interface that contains the return types. Then we can use T extends "keyboardWillShow"
to conditionally set the return types.
type ObjectType<T extends TypeName> =
T extends "keyboardWillShow" ? Observable<KeyboardInfo> :
T extends "keyboardDidShow" ? Observable<void> :
T extends "keyboardWillHide" ? Observable<string> :
T extends "keyboardDidHide" ? Observable<number> :
never;
We can use Generics of Typescript to obtain the type of the parameter, then feed it to ObjectType
, which will give use the generic object types.
function myMethod<T extends TypeName>(model: Keyboard, parameter: T): ObjectType<T> {
return '';
}
const keyboard = new Keyboard();
const test1 = myMethod(keyboard, "keyboardWillShow");
const test2 = myMethod(keyboard, "keyboardDidShow");
const test3 = myMethod(keyboard, "keyboardWillHide");
const test4 = myMethod(keyboard, "keyboardDidHide");
export class Keyboard {
}
type TypeName = "keyboardWillShow" | "keyboardDidShow" | "keyboardWillHide" | "keyboardDidHide";
export interface KeyboardInfo { }
export interface Observable<T> {
test: T;
}
type ObjectType<T extends TypeName> =
T extends "keyboardWillShow" ? Observable<KeyboardInfo> :
T extends "keyboardDidShow" ? Observable<void> :
T extends "keyboardWillHide" ? Observable<string> :
T extends "keyboardDidHide" ? Observable<number> :
never;
function myMethod<T extends TypeName>(model: Keyboard, parameter: T): ObjectType<T> {
return '';
}
const keyboard = new Keyboard();
const test1 = myMethod(keyboard, "keyboardWillShow");
const test2 = myMethod(keyboard, "keyboardDidShow");
const test3 = myMethod(keyboard, "keyboardWillHide");
const test4 = myMethod(keyboard, "keyboardDidHide");