Search code examples
typescriptreflectioninstantiation

How to store "types" and instantiate them dynamically in typescript?


I'm trying to accomplish a similar behavior that I can do with C#. In C# I can use reflection to dynamically instantiate a class based on the Type class. The following code shows how to do in c#, rather simple:

interface IHandler
{
   void Handle();
}
var handlers = new Dictionary<string, Type>
{
  { "querydom", typeof(QueryDomHandler) },
  { "other", typeof(OtherHandler) },
};

var type = handlers["querydom"];
var instance = (IHandler)Activator.CreateInstance(type, args);
instance.Handle();

How can I accomplish the same with typescript? I've sketched the following code, but I do not know how to get a "Type" from a class (QueryDomCommandHandler), or how to instantiate a class dynamically without using its name ( new QueryDomCommandHandler()).

let handlers = [];
handlers[CommandType.QueryDom] = QueryDomCommandHandler; //how to store the "type"?

chrome.runtime.onMessage.addListener((message: Command, sender, sendResponse) => {
    logger.debug(`${isFrame ? 'Frame' : 'Window'} '${document.location.href}' received message of type '${CommandType[message.command]}'`);
 
    const handlerType = handlers[CommandType.QueryDom];
    const handlerInstance = ????? //how to instantiate the class?
    if (message.command == CommandType.QueryDom) {
        const handler = new QueryDomCommandHandler(message as RulesCommand);
        const response = handler.handle();
        sendResponse(response);
        return true;
    }
    else if (message.command == CommandType.Highlight) {
        const handler = new HighlightCommandHandler(message as RulesCommand);
        handler.handle();
    }
});

Any insights?

UPDATE

Thanks to the answers, here's my solution, although I would like to use the enum instead of the hardcoded string in the Record, but couldn't figure it out:

const handlers: Record<string, (new () => commands.CommandHandlerBase)> = {
    'QueryDom': QueryDomCommandHandler,
    'Highlight': HighlightCommandHandler,
    'ClearHighlight': ClearHighlightCommandHandler,
};

    const handlerType = handlers[commands.CommandType[message.command]];
    const handler = new handlerType();
    const response = await handler.handle(message);

Solution

  • It was so simple. You just need to use the Map class instead of the Record:

    type WorkerHandlerConstructor = (new () => handlers.CommandHandlerBase);
    
    handlers: Map<types.CommandType, WorkerHandlerConstructor> = new Map<types.CommandType, WorkerHandlerConstructor>([
        [types.CommandType.Click, handlers.ClickCommandHandler],
        [types.CommandType.Navigate, handlers.NavigateCommandHandler]
    ]);
    

    And then

    const handlerType = handlers.get(types.CommandType.Click);
    const handler = new handlerType();