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)
}
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);
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');
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();
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.
à 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();
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');
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;
}
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();
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()
}