Declare an additional member to a method of a class

Viewed 270

I have decorated a method with a typescript decorator adding a member to the method:

export class MyClass {
    @AddMyMember()
    public myMethod(param1:string) {
        // ...
    }
}

So that I should be able to use it this way:

const myClassInstance = new MyClass();
myClassInstance.myMethod.myMember(); // error of course here, as myMember has not been declared yet...

But I cannot find a way to declare myMember efficiently:

export declare interface MyClass {
// Something great here maybe?....
}
1 Answers

The merging problem

There is no way to declare a member of a class with a different type, declaration merging can only add members not change the types of existing members.

Something we can do is use an intersection type. If two members of an intersection type have the same name and different types the member on the intersection will be an intersection of the two original types.

class _MyClass {
    public constructor (t: string) {

    }
    public myMethod(param1:string) {
    }

    static staticF(){}
}

type ReplaceInstanceType<T extends new(...a: any[])=> any, TNew> = {
    new (... a: (T extends new (...p: infer P) => unknown ? P: [])) : TNew
} & Pick<T, keyof T>

export type MyClass = _MyClass & {
    myMethod: {
        myMember(): void
    }
}


export const MyClass: ReplaceInstanceType<typeof _MyClass, MyClass> = _MyClass as any;

let a = new MyClass("") // can new up
a.myMethod("") // can call myMethod
a.myMethod.myMember(); // can call  myMember
MyClass.staticF

The decorator problem

The second part of your problem is that a decorator can't mutate the type of the member it is applied to (this is by design). You could use a HOC to create the class instead, and pass in a list of member names (compile checked using keyof) to have the extra members added :

type ReplaceInstanceType<T extends new(...a: any[])=> any, TNew> = {
    new (... a: (T extends new (...p: infer P) => unknown ? P: [])) : TNew
} & Pick<T, keyof T>

function addMemebers<T extends new(...a: any[])=> any, K extends keyof InstanceType<T>>(members: K[], cls: T) : ReplaceInstanceType<T, InstanceType<T> & Record<K, {myMember(): void}>>{
    return null as any; // replace with implementation
}
export const MyClass = addMemebers(["myMethod"], class {
    public constructor (t: string) {

    }
    public myMethod(param1:string) {
    }

    static staticF(){}
})


let a = new MyClass("") // can new up
a.myMethod("") // can call myMethod
a.myMethod.myMember(); // can call  myMember
MyClass.staticF
Related