Declare an additional member to a method of a class
Asked Answered
A

1

8

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?....
}
Athwartships answered 6/11, 2018 at 14:2 Comment(2)
Interesting question, the answer must be buried somewhere in typescriptlang.org/docs/handbook/decorators.html or possibly typescriptlang.org/docs/handbook/declaration-merging.htmlHomophonous
So in interface myMethod should have both callable signature and myMember method: myMethod: { (param1: string): void, myMember: () => void }. There still will be an error (Duplicate identifier 'myMethod'.) which you can ignore with // @ts-ignoreDude
C
7

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
Chutney answered 9/11, 2018 at 11:35 Comment(2)
Your answer is awesome and cristal clear, thanks. About the decorator, I already had implemented it, I just needed the type declaration. There is a trick to modify a member on which a decorator is applied: Your decorator could return { configurable: true, get(this:MyClass) { ... } } where MyClass assertion is the host (could use generic types also). As soon as you got the host instance, you can then change everything you want on you member from your decorator :-)Athwartships
@CharlesHETIER I might not have been very clear in the answer, you can modify the runtime JS class, you can't change the static Typescript type.Chutney

© 2022 - 2024 — McMap. All rights reserved.