Tips for efficiently storing "types" and dynamically instantiating them in TypeScript

My goal is to achieve a similar functionality in C#. Reflection can be used in C# to dynamically create an instance of a class based on the Type class. The code snippet below demonstrates how this can be done in C#:

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 the same be achieved using typescript? Below is the code I have come up with, but I am unsure how to obtain the "Type" from a class (QueryDomCommandHandler) or how to dynamically instantiate a class without explicitly referencing its name (new QueryDomCommandHandler()).

let handlers = [];
handlers[CommandType.QueryDom] = QueryDomCommandHandler; //how can the "type" be stored?

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 can the class be instantiated?
    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 suggestions?

UPDATE

Thank you for the responses, here is my solution. Ideally, I would like to use the enum instead of hardcoded strings in the Record, but I haven't been able to figure that 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);

Answer №1

Important Note: In TypeScript, it's important to understand that you cannot access the "type" directly during runtime. Instead, what you actually receive is the constructor of the class.

To ensure proper type inference, you must provide some type information for the handlers array as follows:

let handlers: (new (...arg: any[]) => CommandHandler)[] = [];

Make sure to update the CommandHandler part with the appropriate base handler class name.

Once you have the constructor, creating an instance is simple - just use the new keyword. The actual class name itself isn't crucial:

const handlerInstance = new handlerType()
//    ^? const handlerInstance: CommandHandler

For handling the Update part effectively:

Using the satisfices keyword on the handlers provides the desired outcome:

const handlers = {
    [CommandType.QueryDom]: QueryDomCommandHandler,
    [CommandType.Highlight]: HighlightCommandHandler,
    [CommandType.ClearHighlight]: ClearHighlightCommandHandler
} satisfies Record<CommandType, (new () => commands.CommandHandlerBase)>

if (message.command == CommandType.QueryDom) {
    const handlerType = handlers[message.command];
    const handler = new handlerType();
    //    ^? const handler: QueryDomCommandHandler
}

In addition, enum keys can be used as object keys in a similar manner as shown above.

Note: While using

satisfies Record<string, (new () => commands.CommandHandlerBase)>
achieves a similar effect, specifying CommandType offers better clarity.

Answer №2

It was surprisingly simple. All you have to do is utilize 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]
]);

After that

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

How can I furnish TSC with TypeScript definitions for URI imports in a comprehensive manner?

import Vue from 'https://cdn.jsdelivr.net/npm/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="c5b3b0a085f7ebf0ebf7f4">[email protected]</a>/dist/vue.esm.js' If I submit the above code to TSC for compilat ...

Entering the appropriate value into an object's property accurately

I am currently facing an issue with typing the value needed to be inserted into an object's property. Please refer below. interface SliceStateType { TrptLimit: number; TrptOffset: number; someString: string; someBool:boolean; } inter ...

Optimizing your use of fromCharCode.apply with Uint8Array in TypeScript 3

I recently came across some code that I inherited which appears like this: String.fromCharCode.apply(null, new Uint8Array(license)); Recently, we updated our project dependencies to TypeScript 3, which raised an error stating: Argument of type 'Ui ...

Error message: While running in JEST, the airtable code encountered a TypeError stating that it cannot read the 'bind' property of an

Encountered an error while running my Jest tests where there was an issue with importing Airtable TypeError: Cannot read property 'bind' of undefined > 1 | import AirtableAPI from 'airtable' | ^ at Object.&l ...

Exploring the Functionality of Observables and HTTP Requests in Angular

When I use the http get method in Angular, it returns an observable. To test this, I created a local server that serves up a 50 MB JSON file containing all employee data. import { Injectable } from "@angular/core"; import { Http } from "@angular/http"; im ...

Using TypeScript with makeStyles - how to effectively pass props for styling purposes

Currently, I'm using Material-UI's makeStyles feature in conjunction with TypeScript. After stumbling upon a solution that actually works, here is the snippet of code: export interface StyleProps { md?: any; } const useStyles = makeStyles< ...

Using static typing in Visual Studio for Angular HTML

Is there a tool that can validate HTML and provide intellisense similar to TypeScript? I'm looking for something that can detect errors when using non-existent directives or undeclared scope properties, similar to how TypeScript handles compilation er ...

The operator is being invoked multiple times beyond originally anticipated

I am currently working on developing code that paginates a result set using the expand operator until a specific number of resources have been fetched. Below is the code snippet I have written so far (excluding the actual async call logic): import { Obser ...

Obtaining the component instance ('this') from a template

Imagine we are in a situation where we need to connect a child component property to the component instance itself within a template: <child-component parent="???"></child-component1> Is there a solution for this without having to create a sp ...

The value stored within an object does not automatically refresh when using the useState hook

const increaseOffsetBy24 = () => { setHasMore(false); dispatch(contentList(paramsData)); setParamsData((prevState) => ({ ...prevState, offset: prevState.offset + 24, })); setHasMore(true); }; This function increment ...

What is the best way to ensure that a mapped type preserves its data types when accessing a variable?

I am currently working on preserving the types of an object that has string keys and values that can fall into two possible types. Consider this simple example: type Option1 = number type Option2 = string interface Options { readonly [key: string]: Op ...

Validating nested objects in YUP with the potential for zero or multiple properties present

I am currently working on setting up yup validation for this object: placements: { 3: {}, 5: {}, 6: {0: 'D17'}, 7: {}, 8: {}, 9: {}, 10: {}, 11: {}, } The challenge I am facing is that an entry like 3: {} can be empty, and that's totally fi ...

Tips for sending a function with arguments in a React application using TypeScript

Is there a way to streamline passing a handleClick function to the son component so that it does not need to be repeated? The code in question is as follows: Mother Component: const Mother = () => { const [selectedOption, setSelectedOption] = useSt ...

Activation of Angular SwUpdate deprecation

Within my Angular project, I am currently utilizing the following code snippet: this.swUpdate.available.subscribe(() => { ... }); While this code functions correctly, it does generate a warning regarding the deprecation of activated. How can I addre ...

What is the best way to replicate certain key-value pairs in an array of objects?

I am working with an array of objects. resources=[{name:'x', title:'xx',..},{name:'y',title:'yy',..}..] To populate my HTML tooltip, I am pushing all the titles from the resources array to a new array. dialogOkCli ...

Utilizing environment variables in the root files of your SvelteKit project: A guide

I have encountered an issue with my SvelteKit project which uses TypeScript and includes a .env file at the root. Additionally, I have added a drizzle.config.ts file at the root directory. The problem arises when I try to import DATABASE_HOST from $env/sta ...

The base URL specified in the tsconfig file is causing the absolute path to malfunction

Displayed below is the layout of my folders on the left, along with a metro error in the terminal and my tsconfig.json configuration with baseUrl set to './src'. Additionally, I have included screenshots of my app.ts and MainTabs.ts for further c ...

Ways to access UserProfile in a different Dialogio

For the implementation of a chatbot, I am utilizing Microsoft's Bot Builder framework. However, upon implementing an alternative path to the dialog flow, I noticed that the user's Profile references are getting lost. Here is the code snippet fr ...

Tips for customizing Material UI CSS default properties in React

I'm currently working on a React project and utilizing the 'Table' component from Material UI. The default CSS properties of this table, along with its components like TableHead, TableCell, and TableRow, are proving difficult to override whi ...

The parent class has not been specified

I am facing an issue with my parent class, HTTPConnection, which I intend to use as a network utility class in order to avoid redundant code. However, when attempting to utilize it, the file core.umd.js throws an error stating Uncaught ReferenceError: HTTP ...