Caricamento dinamico di una class typescript (riflessione per typescript)

Mi piacerebbe essere in grado di creare un’istanza di una class typescript in cui ottengo i dettagli della class e del costruttore in fase di esecuzione. La funzione che vorrei scrivere includerà i parametri nome class e costruttore.

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) } 

Potresti provare:

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

Modifica Questa versione funziona utilizzando il campo di gioco di TypeScript, con l’esempio:

 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); 

Dato che stai usando TypeScript, suppongo che tu voglia che l’object caricato venga digitato. Quindi ecco la class di esempio (e un’interfaccia perché si sta scegliendo di caricare una delle molte implementazioni, per esempio).

 interface IExample { test() : string; } class Example { constructor (private a: string, private b: string) { } test() { return this.a + ' ' + this.b; } } 

Quindi useresti una sorta di caricatore per restituirti un’implementazione:

 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; } } 

E quindi caricarlo in questo modo:

 var loader = new InstanceLoader(window); var example =  loader.getInstance('Example', 'A', 'B'); alert(example.test()); 

Al momento, abbiamo un cast: – ma quando vengono aggiunti i generici, potremmo farla franca e utilizzare invece i generici. Assomiglierà a questo (tenendo presente che non fa ancora parte della lingua!)

 class InstanceLoader { constructor(private context: Object) { } getInstance(name: string, ...args: any[]) : T { var instance = Object.create(this.context[name].prototype); instance.constructor.apply(instance, args); return  instance; } } var loader = new InstanceLoader(window); var example = loader.getInstance('Example', 'A', 'B'); 

Aggiornare

Per fare in modo che funzioni nell’ultimo TypeScript ora devi lanciare lo spazio dei nomi su any . Altrimenti si ottiene un Error TS7017 Build:Element implicitly has an 'any' type because type '{}' has no index signature.

Se hai uno spazio dei nomi / modulo specifico, per tutte le classi che vuoi creare, puoi semplicemente fare questo:

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

Aggiornamento: senza uno spazio dei nomi, utilizzare new (window)[classname]()

In TypeScript, se dichiari una class al di fuori di uno spazio dei nomi, genera una var per la “funzione di class”. Ciò significa che è archiviato rispetto all’ambito corrente (molto probabilmente window meno che non lo si esegua in un altro ambito, ad esempio come nodejs). Ciò significa che puoi semplicemente fare una new (window)[classNameString] :

Questo è un esempio funzionante (tutto il codice, nessuno spazio dei nomi):

 class TestClass { public DoIt() { alert("Hello"); } } var test = new (window)["TestClass"](); test.DoIt(); 

Per capire perché funziona, il codice JS generato è simile al seguente:

 var TestClass = (function () { function TestClass() { } TestClass.prototype.DoIt = function () { alert("Hello"); }; return TestClass; }()); var test = new window["TestClass"](); test.DoIt(); 

Funziona in TypeScript 1.8 con il modulo ES6:

 import * as handlers from './handler'; function createInstance(className: string, ...args: any[]) { return new (handlers)[className](...args); } 

Le classi vengono esportate nel modulo handler . Possono essere riesportati da altri moduli.

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

Per quanto riguarda il passaggio del nome del modulo negli arugments, non riesco a farlo funzionare perché il modulo ES6 non può essere caricato dynamicmente.

A partire dal typescript 0.9.1 , puoi fare qualcosa come questo gioco :

 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(); 

Un altro modo sarebbe chiamare il file in modo dinamico e 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'); 
 function fromCamelCase(str: string) { return str // insert a '-' between lower & upper .replace(/([az])([AZ])/g, '$1-$2').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](); } 

o

 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; } 

Ho trovato un altro modo in cui nel mio caso non ho accesso alla finestra.

Classe di esempio che si desidera creare:

 class MyService { private someText: string; constructor(someText: string) { this.someText = someText; } public writeSomeText() { console.log(this.someText); } } 

Classe di fabbrica:

 interface Service { new (param: string): T; } export class ServiceFactory { public createService(ctor: Service, param: string) { return new ctor(param); } } 

E poi per creare l’istanza usando la Factory:

 const factory: ServiceFactory = new ServiceFactory(); const service: MyService = factory.createService(MyService, 'Hello World'); service.writeSomeText(); 

Sto usando typescript ~2.5.3 e sono in grado di farlo:

 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() }