Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript call static method of generic type

I have a service which I want to use to return a object of a specified type build from a JSON.

I have a class MyClass which implements a static class which define a FromJSON static method.

export interface InterfaceMyClass {
    static FromJSON(json: any): any;
}

export class MyClass implements InterfaceMyClass {

    constructor(){}

    FromJSON(json: any): MyClass {
        let instance = MyClass.create(MyClass.prototype);
        Object.assign(instance, json);
        // Some other code specific to MyClass
        return instance;
    }
}

I don't know how to call the static method of the generic class that I've passed in parameter in my service. My service looks like this:

export class MyService<T extends InterfaceMyClass> {
    getObject() {
        let json = getExternalJson(...);
        return T.FromJSON(json); // <-- How to call static method FromJSON from T class ?
    }
}

I would like to use the service this way:

let service = new MyService<MyClass>();
let myObject = service.getObject(); // <-- Should by an MyClass instance (created by MyClass.FromJSON)

Question: How can I call this T.FromJSON method?

Bonus question: What is the good way to implements static method? I dont think that my FromJSON method from MyClass is static. If I add static word before FromJSON, it tells me this:

[ts] Class 'MyClass' incorrectly implements interface 'InterfaceMyClass'.
       Property 'FromJSON' is missing in type 'MyClass'.
like image 857
Ben Avatar asked Oct 21 '16 08:10

Ben


1 Answers

A few things:

(1) Interfaces can't have static declarations, for example:

interface MyInterface {
    static myMethod(); // Error: 'static' modifier cannot appear on a type member
}

(code in playground)

The way to get around this in typescript is to define a builder/constructor interface:

interface MyInterface {}

interface MyInterfaceBuilder {
    new (): MyInterface;
    myMethod();
}

(2) Generic constraints are only available in compilation time, but then the compiler removes them as javascript doesn't support it, for example:

class MyClass<T extends string> {
    private member: T;

    constructor() {
        this.member = new T(); // Error: Cannot find name 'T'
    }
}

compiles into:

var MyClass = (function () {
    function MyClass() {
        this.member = new T(); // Error: Cannot find name 'T'
    }
    return MyClass;
}());

(code in playground)

When looking at the js output it's clear why the compiler throws the Cannot find name 'T' error, as T is nowhere to be found.

To get around all of that, here's a solution for you:

interface IMyClass {}

interface IMyClassBuilder<T extends IMyClass> {
    new (): T;
    FromJSON(json: any): any;
}

class MyClass implements IMyClass {
    static FromJSON(json: any) {
        return "";
    }
}

class MyService<T extends IMyClass> {
    private classToCreate: IMyClassBuilder<T>;

    constructor(classToCreate: IMyClassBuilder<T>) {
        this.classToCreate = classToCreate;
    }

    getObject(): T {
        let json = getExternalJson(...);
        return this.classToCreate.FromJSON(json);
    }
}

let service = new MyService(MyClass);
let myObject = service.getObject();

(code in playground)

like image 91
Nitzan Tomer Avatar answered Oct 12 '22 18:10

Nitzan Tomer