Chargement dynamique d'une classe typescript (réflexion pour typescript)

je voudrais pouvoir instancier une classe typescript où j'obtiens les détails de la classe et du constructeur à l'exécution. La fonction que j'aimerais écrire prendra le nom de la classe et les paramètres du constructeur.

export function createInstance(moduleName : string, className : string, instanceParameters : string[]) {
    //return new [moduleName].[className]([instancePameters]); (THIS IS THE BIT I DON'T KNOW HOW TO DO)
}
23
demandé sur Greg van Berkel 2013-03-11 16:30:20

9 réponses

Vous pourriez essayer:

var newInstance = Object.create(window[className].prototype);
newInstance.constructor.apply(newInstance, instanceparameters);
return newInstance;

Modifier Cette version est de travailler sur le Tapuscrit de l'aire de jeux, avec l'exemple:

class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}

//instance creation here
var greeter = Object.create(window["Greeter"].prototype);
greeter.constructor.apply(greeter, new Array("World"));

var button = document.createElement('button');
button.innerText = "Say Hello";
button.onclick = function() {
    alert(greeter.greet());
}

document.body.appendChild(button);
14
répondu Colin Dumitru 2018-06-12 20:11:22

comme vous utilisez le Typographie, je suppose que vous voulez que l'objet chargé soit tapé. Voici donc la classe example (et une interface parce que vous choisissez de charger l'une des nombreuses implémentations, par exemple).

interface IExample {
    test() : string;
}

class Example {
    constructor (private a: string, private b: string) {

    }

    test() {
        return this.a + ' ' + this.b;
    }
}

alors vous utiliserez une sorte de loader pour vous rendre une implémentation:

class InstanceLoader {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return instance;
    }
}

Et puis la charger comme ceci:

var loader = new InstanceLoader(window);

var example = <IExample> loader.getInstance('Example', 'A', 'B');
alert(example.test());

pour le moment, nous avons un cast: <IExample> - mais lorsque les génériques sont ajoutés, nous pourrions supprimer cette utilisez plutôt des génériques. Il ressemblera à ceci (en gardant à l'esprit qu'il ne fait pas partie de la langue encore!)

class InstanceLoader<T> {
    constructor(private context: Object) {

    }

    getInstance(name: string, ...args: any[]) : T {
        var instance = Object.create(this.context[name].prototype);
        instance.constructor.apply(instance, args);
        return <T> instance;
    }
}

var loader = new InstanceLoader<IExample>(window);

var example = loader.getInstance('Example', 'A', 'B');
16
répondu user75525 2013-03-11 14:38:22

mise à Jour

pour que cela fonctionne dans la dernière version dactylographiée, vous devez maintenant lancer le namespace vers any. Sinon vous obtenez un Error TS7017 Build:Element implicitly has an 'any' type because type '{}' has no index signature.

Si vous avez un namespace/module spécifique, pour toutes les classes que vous voulez créer, vous pouvez simplement faire ceci:

var newClass: any = new (<any>MyNamespace)[classNameString](parametersIfAny);

mise à Jour: Sans un espace de noms à utiliser new (<any>window)[classname]()

en TypeScript, si vous déclarez une classe en dehors d'un namespace, il génère un var pour la "fonction de classe". Que signifie qu'il est stocké par rapport à la portée actuelle (très probablement window sauf si vous l'exécutez sous un autre scope, par exemple comme nodejs). Cela signifie que vous pouvez simplement faire new (<any>window)[classNameString]:

ceci est un exemple de travail (tout le code, pas de namespace):

class TestClass
{
    public DoIt()
    {
        alert("Hello");
    }
}

var test = new (<any>window)["TestClass"]();
test.DoIt();

pour voir pourquoi cela fonctionne, le code JS généré ressemble à ceci:

var TestClass = (function () {
    function TestClass() {
    }
    TestClass.prototype.DoIt = function () {
        alert("Hello");
    };
    return TestClass;
}());
var test = new window["TestClass"]();
test.DoIt();
12
répondu Gone Coding 2018-06-12 20:22:56

cela fonctionne à la typographie 1.8 avec le module ES6:

import * as handlers from './handler';

function createInstance(className: string, ...args: any[]) {
  return new (<any>handlers)[className](...args);
}

les Classes sont exportées en handler module. Ils peuvent être réexportés à partir d'autres modules.

export myClass {};
export classA from './a';
export classB from './b';

pour ce qui est de passer le nom du module dans les plugins, Je ne peux pas le faire fonctionner parce que le module ES6 ne peut pas être chargé dynamiquement.

10
répondu aleung 2016-03-04 03:56:30

à partir de l'écriture dactylographiée 0.9.1, vous pouvez faire quelque chose comme ceci aire de jeux:

class Handler {
    msgs:string[];  
    constructor(msgs:string[]) {
        this.msgs = msgs;
    }
    greet() {
        this.msgs.forEach(x=>alert(x));
    }
}

function createHandler(handler: typeof Handler, params: string[]) {
    var obj = new handler(params);
    return obj;
}

var h = createHandler(Handler, ['hi', 'bye']);
h.greet();
4
répondu Joe 2013-09-21 08:32:22

Un autre moyen serait d'appeler le fichier dynamiquement et new

// -->Import: it dynamically
const plug = await import(absPath);
const constructorName = Object.keys(plug)[0];

// -->Set: it
const plugin = new plug[constructorName]('new', 'data', 'to', 'pass');
0
répondu Pian0_M4n 2018-01-28 14:59:22
function fromCamelCase(str: string) {
  return str
    // insert a '-' between lower & upper
    .replace(/([a-z])([A-Z])/g, '-').toLowerCase();
}

async getViewModelFromName(name: string) {
    //
    // removes the 'ViewModel' part ('MyModelNameViewModel' = 'MyModelName').
    let index = name.indexOf('ViewModel');
    let shortName = index > 0 ? name.substring(0, index) : name;

    // gets the '-' separator representation of the camel cased model name ('MyModelName' = 'my-model-name').
    let modelFilename = fromCamelCase(shortName) + '.view-model';

    var ns = await import('./../view-models/' + modelFilename);

    return new ns[name]();
  }

ou

declare var require: any; // if using typescript.

getInstanceByName(name: string) {
    let instance;

    var f = function (r) {
      r.keys().some(key => {
        let o = r(key);
        return Object.keys(o).some(prop => {
          if (prop === name) {
            instance = new o[prop];
            return true;
          }
        })
      });
    }
    f(require.context('./../view-models/', false, /\.view-model.ts$/));

    return instance;
}
0
répondu Ivan Carmenates García 2018-04-25 19:47:32

j'ai trouvé un autre moyen car dans mon cas je n'ai pas accès à la fenêtre.

Exemple de classe qui veulent être créé:

class MyService {

  private someText: string;

  constructor(someText: string) {
    this.someText = someText;
  }

  public writeSomeText() {
    console.log(this.someText);
  }
}

classe de L'usine:

interface Service<T> {
  new (param: string): T;
}

export class ServiceFactory<T> {

  public createService(ctor: Service<T>, param: string) {
    return new ctor(param);
  }

}

et ensuite de créer l'instance en utilisant L'usine:

const factory: ServiceFactory<MyService> = new ServiceFactory<MyService>();
const service: MyService = factory.createService(MyService, 'Hello World');
service.writeSomeText();
0
répondu Maximiliano De Lorenzo 2018-06-22 17:41:05

j'utilise le typographie ~2.5.3 et je suis capable de faire ceci:

class AEmailNotification implements IJobStateEmailNotification {}
class ClientJobRequestNotification extends AEmailNotification {}
class ClientJobRequestAcceptedNotification extends AEmailNotification {}
class ClientJobRequestDeclinedNotification extends AEmailNotification {}
class ClientJobRequestCounterOfferNotification extends AEmailNotification {}
class ClientJobRequestEscrowedFundsNotification extends AEmailNotification {}
class ClientJobRequestCommenceNotification extends AEmailNotification {}

export function notificationEmail(action: string) {
    console.log(`+ build factory object for action: ${action}`)

    const actions = {}

    actions['Create job'] = ClientJobRequestNotification
    actions['Accept terms'] = ClientJobRequestAcceptedNotification
    actions['Decline terms'] = ClientJobRequestDeclinedNotification
    actions['Counter offer'] = ClientJobRequestCounterOfferNotification
    actions['Add funds to escrow'] = ClientJobRequestEscrowedFundsNotification
    actions['-- provider to commence the job --'] = ClientJobRequestCommenceNotification

    const jobAction = actions[action]

    if (!jobAction) {
        console.log(`! unknown action type: ${action}`)
        return undefined
    }

    return new jobAction()
}
0
répondu Gus 2018-08-03 01:05:28