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

Angular 4's Mddialog experiencing intermittent display problem

While using MDDialog in my Angular app, I've encountered a couple of issues. Whenever a user clicks on the div, flickering occurs. Additionally, if the user then clicks on one of the buttons, the afterclose event is not triggered. Can anyone provide ...

What could be causing my function to return as undefined the second time it's invoked?

Within my approach private onDataLoadSuccessful(users: User[], roles: Role[]) { this.alertService.stopLoadingMessage(); this.loadingIndicator = false; this.dataSource.data = users.map(data => { let newData: User; newData = ...

Using JavaScript, generate an array of objects that contain unique values by incrementing each value by 1

I have an array of objects called arrlist and a parameter uid If the property value has duplicate values (ignoring null) and the id is not the same as the uid, then autoincrement the value until there are no more duplicate values. How can I achieve the a ...

Combining individual TypeScript files together

Recently, I encountered an issue with 2 typescript files: a.ts: let some : string = "some"; b.ts: console.log(some); Surprisingly, when compiling both files together by including them in the tsconfig or command line, there was no error about 'som ...

What is the best way to utilize await in promises instead of using then?

How can I correctly handle the Promise.all() method? I'm experiencing issues with resolving the promise that generates a series of asynchronous requests (simple database queries in supabase-pg SQL). After iterating through the results with a forEach l ...

What is the best method for altering a route in React while utilizing Typescript?

I recently started coding along with the ZTM course and am working on a face recognition app. Instead of using JavaScript, I decided to use TypeScript for this project in order to avoid using the any type. However, as a beginner in this language, I'm ...

Revamping elements according to ordered array. Angular version 4.3

Dealing with an array of data that needs to be sorted for displaying in a component seems to be a challenge. Despite having a functional code sample demonstrating the concept, the sorting is not reflected in the Angular app's DOM. The original data i ...

The GIPHY API object returns no results

Utilizing Angular 2 to fetch data from the GIPHY API. export class ListaGifsComponent { gifs : Object[] = []; urlBase = "http://api.giphy.com/v1/gifs/search?q="; termoPesquisado = "ryan+gosling"; key = "O8RhkTXfiSPmSCHosPAnhO70pdnHUiWn"; ...

Leveraging scanner-js within an Angular2 environment

Exploring ways to incorporate Scanner-JS into my Angular2 project, a tool I discovered while tinkering with the framework. Being a novice in Angular2, this question might be elementary for some. I successfully installed scanner-js via npm npm install sc ...

Distributing utility functions universally throughout the entire React application

Is there a way to create global functions in React that can be imported into one file and shared across all pages? Currently, I have to import helper.tsx into each individual file where I want to use them. For example, the helper.tsx file exports functio ...

Angular - Implementing filter functionality for an array of objects based on multiple dropdown selections

I am currently working on filtering an array of objects based on four fields from a form. These four fields can be combined for more specific filtering. The four fields consist of two dropdowns with multiple selection options and two text boxes. Upon cli ...

What is the reason for a boolean extracted from a union type showing that it is not equivalent to true?

I'm facing a general understanding issue with this problem. While it seems to stem from material-ui, I suspect it's actually more of a typescript issue in general. Despite my attempts, I couldn't replicate the problem with my own types, so I ...

Issues arising with utilizing Github for hosting Angular applications

After developing a site with Angular, everything was running smoothly on my local host. However, when I decided to host the site on GitHub, two errors appeared. You can access my site through this link: Here is a screenshot of the errors encountered [1]: ...

What steps can be taken to ensure that all object properties become reactive?

Let's dive into this simplified scenario: interface Pup { name: string; age: number; } const puppy: Pup = { name: 'Rex', age: 3, }; The goal here is to establish a reactive link for each attribute within the puppy object. The usua ...

Verify the legitimacy of the object

I'm encountering an issue while attempting to create a function that verifies the validity of an object. const isPeriodValid = (period: Period | null): boolean => { return period && period.start && period.end; } export interfac ...

Styling a <slot> within a child component in Vue.js 3.x: Tips and tricks

I'm currently working on customizing the appearance of a p tag that is placed inside a child component using the slot. Parent Component Code: <template> <BasicButton content="Test 1234" @click="SendMessage('test') ...

A universal TypeScript type for functions that return other functions, where the ReturnType is based on the returned function's ReturnType

Greetings to all TypeScript-3-Gurus out there! I am in need of assistance in defining a generic type GuruMagic<T> that functions as follows: T represents a function that returns another function, such as this example: fetchUser(id: Id) => (disp ...

Utilizing React-hook-Form to transfer data between two SelectBoxes

This simple logic is causing me some trouble. Despite using react-hook-form, I thought this would be easy. However, after struggling with it for over a week, I'm still facing challenges. I'm incorporating nextUI components into my project. < ...

DuplicateModelError: Unable to duplicate model after it has been compiled, React.js, MongoDB, TypeScript

In the early stages of developing an application using Next.js, Mongoose, and Typescript, I encountered a persistent issue. Whenever I attempt to send a request through Postman after clicking save, it fails, displaying the error message: OverwriteModelErr ...

A peculiar quirk with Nuxt.js radio buttons: they appear clickable but are somehow not disabled

I’m having an issue with a radio button that won’t check. It seems to be working fine on other pages, but for some reason it just won't click here. <div class="form-group"> <label class="control-label&q ...