How can I dynamically add a named property to a class during runtime and ensure that TypeScript recognizes it?

Within my coding project, I have implemented a decorator that alters a class by adding additional methods to it, specifically in the class A. However, when utilizing an instance of this class, the added methods do not show up in the autocomplete feature. Additionally, compiler errors arise due to these properties not being recognized.

I am aware that decorators are incapable of changing the type of a class, as this functionality is not currently supported. Utilizing mixins is also not preferable in this scenario, as the code will eventually be packaged into a library for use by others. It would be more straightforward if users could simply employ the @ notation.

For instance:

function TypeChangeDecorator(constructor: any) {
  const keys = Reflect.getMetadata(SOME_KEY_SYMBOL, TypeB);
  return class extends constructor {
    private someClass: TypeA;
    
    constructor(...args: any[]) {
      super(...args);
      
      for(key in keys) {
        this[`set${key}`] = () => {} // some method
      }
    }

    someMethod() {
      // implementation here
    }
  }
}

@TypeChangeDecorator
class A {}

Although I understand that it may not be feasible, I am seeking a way to access the methods someMethod and set${key}. Ideally, I would like to avoid compiler errors when attempting to access these methods and have them readily available in autocomplete.

PS: While current information suggests that this feature is not yet supported, I am open to any insights or suggestions regarding this matter.

Answer №1

If you're seeking a solution that doesn't involve using mixins, consider utilizing the interface trick.

interface PropertiesAddedByDecorator {
  someMethod(): any;
}

interface A extends PropertiesAddedByDecorator;

@ TypeChangeDecorator
class A {}

// now when you do
const inst = new A();
inst.someMethod() // no error and autocomplete support

When it comes to utilizing set${key}, it's not feasible. Instead, you can utilize Record<string, Function> to prevent compiler errors.

Additionally, take note of the suggestion from jcalz, where you can declare someMethod: () => {} inside A rather than declaring interfaces.

Answer №2

Currently in TypeScript 5.4, decorators are still unable to change the type of the class they decorate. The long-standing feature request microsoft/TypeScript#4881 remains unresolved. Until this feature is added, options are limited to either giving up or finding a workaround (such as using a function that modifies a class, like a mixin).

Answer №3

While the decorator syntax with @ may not be usable, a similar functionality can still be achieved:

interface Constructor<T> {
  new (...args: any[]): T;
}

function addFunctionality<T extends Constructor<any>>(BaseClass: T) {
  class Extended extends BaseClass {
    customFunction() {
      console.log(`Custom functionality added for ${this.name}, aged ${this.age}.`);
    }
  };
  return Extended;
}

class User {
  constructor(public name: string, public age: number) {}
  introduce() {
    console.log(`Hi, I'm ${this.name}`);
  }
}

class AdditionalFeature {
  customFunction() {
    console.log(`Custom functionality added.`);
  }
}

const ExtendedUser = addFunctionality(User);
const newUser = new ExtendedUser("Alice", 28);
newUser.introduce();
newUser.customFunction();

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

Error message in Vue3 with TypeScript: When using global components, you may encounter the error "JSX element type '___' does not have any construct or call signatures.ts(2604)."

Each globally registered component suddenly displays a red TypeScript squiggle in the markup! Surprisingly, the app continues to function without any visible errors! This issue arises with all components, whether they are custom-made or third-party libr ...

Angular5+ Error: Unable to retrieve summary for RouterOutlet directive due to illegal state

When attempting to build my Angular App using ng build --prod --aot, I consistently encounter the following error: ERROR in : Illegal state: Could not load the summary for directive RouterOutlet in C:/Path-To-Project/node_modules/@angular/Router/router.d. ...

Typescript encounters Duplicate error when method overloading is implemented

Below is the code snippet that I am working with: public async insert(data: iFlower | iFlower[]): Promise<iFlower> | Promise<iFlower[]> { await this.insert(data); } private async insert(data: iFlower): Promise<iFlower>{ .... return d ...

Creating a local HTML file using node.js: A step-by-step guide

Recently, I've delved into developing games using Typescript. However, I've encountered a bit of an issue when attempting to build my game - it requires running on a server. This limitation prevents me from creating an offline game with Node.js a ...

Unable to transfer variable from a function to the test in Protractor

Currently, I am working on a test to verify the amount of gold in my possession. The test is being conducted using TypeScript and Protractor. Within this testing scenario, I have a method named GetAmountOfChips: public static GetAmountOfChips(): PromiseL ...

Using `it` with accessing class members

When testing whether a specific object/class is correctly wired up, I often utilize it.each to prevent writing repetitive tests. The issue arises when the type of the object doesn't have an index signature, requiring me to cast it to any for it to fun ...

I am uncertain about how to interpret this method signature

Can you help me determine the correct method signature for handleError? The linter tslint is indicating an error message that says expected call-signature: 'handleError' to have a typedef (typedef). Here is the code snippet in question: import ...

When intercepted, the HttpRequest is being canceled

Solution Needed for Request Cancellation Issue Encountering a problem with the usage of HttpInterceptor to include the Authorize header in all HTTP requests sent to AWS API Gateway. The issue arises as all these requests are getting cancelled when interce ...

What is the best way to connect a series of checkboxes within a form utilizing Angular?

I created a form with checkboxes that allow users to select multiple options. However, when I submit the form, instead of receiving an array of objects representing the checked checkboxes, I'm not getting anything at all. Here is what I see in the co ...

Changing the font family for a single element in Next.js

One unique aspect of my project is its global font, however there is one element that randomly pulls font families from a hosted URL. For example: https://*****.com/file/fonts/Parnian.ttf My page operates as a client-side rendered application (CSR). So, ...

Angular developers are struggling to find a suitable alternative for the deprecated "enter" function in the drag and drop CDK with versions 10 and above

By mistake, I was working on an older version of Angular in StackBlitz (a code-pane platform). I came across a function called enter on GitHub, but it didn't solve my issue. I was working on a grid-based drag and drop feature that allows dragging bet ...

Expand the font manually

Is there a way to define a type that represents the widened version of another type? Consider the following scenario: function times<A extends number, B extends number>(a: A, b: B): A & B; The intention behind this times function is to preserv ...

ionChange - only detect changes made in the view and update the model in Ionic 2

In my Ionic 2 application, I have a feature that allows users to schedule notifications as reminders. The requirements for this feature are as follows: Upon entering the reminder page, it should check if there is a saved reminder. If a reminder is saved ...

Setting up TypeScript in Jest without the need for webpack

Currently, I'm developing an NPM module using TypeScript without the use of Webpack for compiling scripts. I need some guidance on configuring Jest to properly run tests with TypeScript files. Any recommendations? // test.spec.ts import {calc} from ...

Is there a way to transfer ngClass logic from the template to the TypeScript file in Angular?

I am implementing dropdown filters for user selection in my Angular application. The logic for adding classes with ngClass is present in the template: <div [ngClass]="i > 2 && 'array-design'"> How can I transfer this ...

Enter the text within the TypeScript file where the cursor is positioned

I am interested in exploring certain inferred types within TypeScript code. This would be particularly beneficial for ensuring that the inferred types are precise, specific, and accurate in correct files. Is there a way to retrieve the type at a specific ...

What is the best way to create a generic that can handle readonly types efficiently?

When performing an action on a list type PerformActionOn<L extends any[]> = L The approach I am taking is as follows: const keys = { a: ['a', 'b', 'c'] as ['a', 'b', 'c'], d: ['d ...

Implicated Generic in TypeScript

Is there a way to simplify my class A implementation? export class A<TB extends B<TC>, TC> implements TD<TB, TC> { make(): TC {} } Currently, I have to specify the TC type every time I create an instance of A: class CTest {} class BTes ...

Updating Angular 5 Views Dynamically Using a While Loop

I am facing an issue in my app where I have a progress bar that updates using a while loop. The problem is that the view only updates after the entire while loop has finished running, even though I am successfully changing the update progress value with ea ...

Why is it that TypeScript struggles to maintain accurate type information within array functions such as map or find?

Within the if block in this scenario, the p property obtains the type of an object. However, inside the arrow function, it can be either an object or undefined. const o: { p?: { sp?: string } } = { p: {} } if (o.p) { const b = ['a'].map(x => ...