What is the method for adding attributes to a class dynamically in TypeScript so that they can be accessed by instances?

I attempted to create a universal factory function that generates custom enum objects, but the instances were not able to retrieve the correct properties.

Take a look at the preview of the code online: https://stackblitz.com/edit/typescript-rkl1zr

Working Environment

Using typescript version 3.6.2

Below is the code snippet:

type IConfig<T> = {
  [key in keyof T]: IOption
}

export interface IOption {
  value: number
  label: string
}

class Factory<T> {
  [key: keyof T]: any // An index signature parameter type must be 'string' or 'number'.
  config: IConfig<T>
  options: IOption[]
  constructor(config: IConfig<T>) {
    this.options = []
    this.config = config
    for (let key in config) {
      this.options.push(config[key])
      this[key] = config[key].value // Type 'number' is not assignable to type 'this[Extract<keyof T, string>]'.
    }
  }
}

const data = {
  NORMAL: {
    value: 1,
    label: 'NORMAL'
  },
  ABNORMAL: {
    value: 0,
    label: 'ABNORMAL',
  },
}

const CommonStatus = new Factory<typeof data>(data)

CommonStatus.NORMAL // No Intelligent Tips

I am expecting the CommonStatus object to have the appropriate type so that I can access the declared properties (NORMAL and ABNORMAL) without encountering any errors.

Answer №1

When looking at the code provided, it seems unnecessary to consider T if you only ever examine keyof T. The property value types of T are consistently ignored, so there may not be a need for the Factory type to carry them around. Instead of using Factory<T>, I propose changing it to Factory<K extends string>, and instead of IConfig<T>, switch it to Record<K, IOption>. If your use case requires consideration of the value types of T[keyof T], feel free to retain the original approach as it does not impact the remainder of the solution.


Unfortunately, there is no straightforward and entirely type-safe solution available in this scenario. TypeScript does not support classes extending arbitrary generic types, even with the use of mixins. You can only extend types with known static keys, which is not the case with something like K, where keys are only known dynamically.

One potential workaround involves utilizing type assertions within the class implementation to compensate for the inability to have arbitrary keys in this, followed by another type assertion to create a user-accessible version of the class that behaves as desired.

Here is the proposed solution:

class _Factory<K extends string> {
  config: Record<K, IOption>;
  options: IOption[];
  constructor(config: Record<K, IOption>) {
    this.options = [];
    this.config = config;
    for (let key in config) {
      this.options.push(config[key]);
      this[key as keyof this] = config[key].value as any; // assertion
    }
  }
}

type Factory<K extends string> = _Factory<K> & Record<K, number>;
const Factory = _Factory as new <K extends string>(
  config: Record<K, IOption>
) => Factory<K>;

By renaming Factory<K> to

_Factory<K></code, we avoid the attempt to incorporate dynamic keys within it. Inside the constructor, various type assertions are utilized during assignment to prevent compiler complaints.</p>

<p>Following these adjustments, we introduce a type named <code>Factory<K>
along with a value named Factory that mimic the intended versions. The type Factory<K> represents an intersection between _Factory<K> and Record<K, number>, providing objects of type Factory<K> with both the config and options properties from _Factory<K>, as well as numeric-valued properties in K.

Now, let's assess its functionality:

const data = {
  NORMAL: {
    value: 1,
    label: "NORMAL",
    c: 1
  },
  ABNORMAL: {
    value: 0,
    label: "ABNORMAL"
  }
};

const CommonStatus = new Factory(data);
CommonStatus.ABNORMAL; // number
CommonStatus.NORMAL; // number
CommonStatus.config.ABNORMAL; // IOption
CommonStatus.config.NORMAL; // IOption
CommonStatus.options; // IOption[]

The results appear satisfactory. Wishing you the best of luck with this! Feel free to reach out if you need further assistance.

Link to code

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

Finding Nested Key Paths in TypeScript for Objects and Arrays

I am in search of a unique method to create a TypeScript type that can take an object type and retrieve all the nested key paths, including properties within arrays as well. I want to exclude any default array properties such as "push" or "pop." While I ha ...

Using TypeScript to destructure by providing types

I encountered an issue while trying to destructure some code. The error message Property 'name' does not exist on type '{}'. is appearing. I thought about using let user:any = {}; as a workaround, but that goes against the eslint rule o ...

Encountered a runtime error while processing 400 requests

Current Situation: When authenticating the username and password in my Ionic 2 project using WebApi 2 token authentication, a token is returned if the credentials are correct. However, a 400 bad request error is returned if the credentials are incorrect. ...

Efficient Ways to Utilize Global CSS in an Angular Project Without CLI

I am utilizing ASP.NET MVC as the server and Angular as the client application. Instead of a static index.html file, I have index.cshtml. The styles I am using are global styles rather than component-scoped. My query revolves around working with a bunch ...

Transforming two child arrays within an object into a single array using Ramda

I am looking to transform an object into an array. The object I have is structured like this: const data = { t1: [ {"a": 1, "a1": 2}, {"b": 3, "b1": 4}, {"c": 5, "c1": 6} ], t2: [ {" ...

Inject components in Angular using dependency injection

Can components in Angular be dependency injected? I am interested in a solution similar to injecting services, like the example below: my.module.ts: providers: [ { provide: MyService, useClass: CustomService } ] I attempted using *ngIf= ...

The 'DOCUMENT' module (imported as 'i23') could not be located within '@angular/platform-browser'

During my upgrade from Angular version 7 to 8, I encountered an error when building the project even though I am not using DOCUMENT. It seems like something is causing this issue that I am overlooking. I have thoroughly checked all the files and confirmed ...

Enhance User Experience - Automatically highlight the first element in Prime NG Menu when activated

I have been working on transitioning the focus from the PrimeNG menu to the first element in the list when the menu is toggled. Here is what I've come up with: In my template, I added: <p-menu appendTo="body" #menu [popup]="true&quo ...

How can I ensure I am receiving real-time updates from a Resolver Service by subscribing and staying in sync with the

How can I effectively implement this code without encountering an error? "Property 'resolve' in type 'DocumentaryResolverService' is not assignable to the same property in base type 'Resolve'." import { Documentary } from ...

Troubleshooting the "Request failed with status code 500" error when refreshing a page in a React application

Every time the page is reloaded, an error message pops up saying: Uncaught (in promise) Error: Request failed with status code 500. Here's the code in list.tsx: const [state, setState] = useState([]); const { getRoom } = useRoom(); const fe ...

Transforming an ordinary JavaScript object into a class instance

As I was delving into Angular's documentation on "Interacting with backend services using HTTP", I came across the following statement in the "Requesting a typed response" section: ...because the response is a plain object that cannot be automatical ...

Issue encountered with the inability to successfully subscribe to the LoggedIn Observable

After successfully logging in using a service in Angular, I am encountering an error while trying to hide the signin and signup links. The error message can be seen in this screenshot: https://i.stack.imgur.com/WcRYm.png Below is my service code snippet: ...

The property of the Angular Typescript object is distinctly not defined, yet the property itself is

Currently facing a bizarre issue. Even though the console displays data in an object from a subscribed observable, TypeScript code shows it as undefined. Take a look: initData(): void { this.backendService.getData().subscribe((depotDays: DepotDayAcc ...

Iterating through a for loop in Angular2 to send multiple GET requests to a Django backend

Currently, I'm facing a challenge with performing multiple GET requests using Angular2 within a Django/Python environment. After successfully making an API request and retrieving a list of users to determine the current user's ID, I utilize a .f ...

Adding an Icon to the Angular Material Snackbar in Angular 5: A Step-by-Step Guide

I recently started using Angular and have incorporated Angular Material Design for my UI elements. Within my application, I am utilizing a snackbar component. However, I am facing difficulty in adding an icon inside the snackbar despite trying various so ...

Data is not displaying correctly in the Angular Material Table

I'm currently trying to build a mat table based on an online tutorial, but I'm facing a problem where the table appears empty even though I have hard coded data. As someone new to Angular and HTML/CSS, I'm struggling to understand why the ma ...

What is the most effective way to condense these if statements?

I've been working on a project that includes some if statements in the code. I was advised to make it more concise and efficient by doing it all in one line. While my current method is functional, I need to refactor it for approval. Can you assist me ...

What is the best way to prevent double clicks when using an external onClick function and an internal Link simultaneously

Encountering an issue with nextjs 13, let me explain the situation: Within a card component, there is an external div containing an internal link to navigate to a single product page. Using onClick on the external div enables it to gain focus (necessary f ...

understanding the life cycle of components in Ionic

I created a component with the following structure: export class AcknowledgementComponent implements AfterViewInit { private description: string; @Input('period') period: string; constructor() { } ngAfterViewInit() { console.log ...

Angular - How child components can communicate with their parent components

I'm struggling to understand how to communicate between components and services. :( Even though I've read and tried a lot, some examples work but I still don't grasp why (?). My goal is to have one parent and two child components: dashboa ...