1

I'm attempting to instantiate classes via configuration with a utility method that looks like this.

Util.newClassByName('MyClass')

It seems that the code works as far as instantiating my class, but I get a Typescript error that I'm not sure how to resolve.

app\scripts\scraper\util.ts(18,20): error TS2322: Type 'Util' is not assignable to type 'void'.

My helper methods which is based on code I found via instantiate-a-javascript-object-using-a-string-to-define-the-class-name, is written in TypeScript.

If I put :void into the method name, I get the error that you see on line 18

class Util {

    public static newClassByName(className): void {
        var namespaceSegment = className.split(".");

        var fn = (window || this);

        for (var i = 0, len = namespaceSegment.length; i < len; i++) {
            fn = fn[namespaceSegment[i]];
        }

        if (typeof fn !== "function") {
            throw new Error("function not found");
        }

        return new fn();   // <-- Line 18
    };
}

If I REMOVE :void from the method then I get this error on the calling code

var scraper = Util.newClassByName(config.scraper);

app\scripts\scraper\scraper-util.ts(68,23): error TS2350: Only a void function can be called with the 'new' keyword.

class Util {

    public static newClassByName(className) {
        var namespaceSegment = className.split(".");

        var fn = (window || this);

        for (var i = 0, len = namespaceSegment.length; i < len; i++) {
            fn = fn[namespaceSegment[i]];
        }

        if (typeof fn !== "function") {
            throw new Error("function not found");
        }

        return new fn();
    };
}
Community
  • 1
  • 1
David Cruwys
  • 6,262
  • 12
  • 45
  • 91

1 Answers1

1

I'd suggest this:

class Scraper {
    constructor(myProp: number) {
        this.MyProp = myProp;
    }
    public MyProp: number;
    public MyFunc = () => console.log(this.MyProp);
}

var config = {
    scraper: "the.correct.namespace.here"
}

window["the"] = {
    "correct": {
        "namespace": {
            "here": Scraper
        }
    }
}

class Util {
    public static getClassByName<T>(className): { new (...args: any[]): T } {
        var namespaceSegment = className.split(".");

        var fn = (window || {});

        for (var i = 0, len = namespaceSegment.length; i < len; i++) {
            fn = fn[namespaceSegment[i]];
            if (fn === undefined) {
                throw new Error("class not found");
            }
        }

        if (typeof fn !== "function") {
            throw new Error("class not found");
        }

        return fn as { new (...args: any[]): T };
    }
}

var myScraperClass = Util.getClassByName<Scraper>(config.scraper)
var scraper = new myScraperClass(23);
scraper.MyFunc();

Here we use the newable interface { new (...args: any[]): T } together with some generics to let you define the type of the resulting object.

Alex
  • 14,104
  • 11
  • 54
  • 77
  • Whilst this looks like it is in the right direction, unfortunately this code did not work, I tried to get it working over last hour, but I'm not fully conversant with generics in typescript, so I'm a little lost, I tried to get a working test JSFiddle/JSBin of this as well, but none of these tools are all that good with typescript – David Cruwys Nov 07 '16 at 22:44
  • 1
    This code works in [typescript playground](http://www.typescriptlang.org/play/) as long as window.config.scraper is properly initialized. `type Scraper { ... }` will not do it, you have to replace it with something like `class Scraper { MyProp: number; }; (window).config = { scraper: Scraper }` – artem Nov 08 '16 at 01:18
  • @DavidCruwys I'm sorry, of course I've should have given you an executable example. I've edited my answer. – Alex Nov 08 '16 at 07:58