Enhance your TypeScript classes with decorators that can add new methods to your class

Can you explain property definition using TypeScript and decorators?

Let's take a look at this class decorator:

function Entity<TFunction extends Function>(target: TFunction): TFunction {
    Object.defineProperty(target.prototype, 'test', {
        value: function() {
            console.log('test call');
            return 'test result';
        }
    });
    return target;
}

Here is how it's used:

@Entity
class Project {
    //
}

let project = new Project();
console.log(project.test());

After running this code, you'll see the following output in the console:

test call            entity.ts:5
test result          entity.ts:18

Although the code works correctly, TypeScript shows an error:

entity.ts(18,21): error TS2339: Property 'test' does not exist on type 'Project'.

Do you know how to resolve this issue?

Answer №1

As far as my current knowledge goes, this capability is currently not feasible. There is an ongoing discussion regarding this matter which can be found here: discussion.

Therefore, one option is to refrain from using decorators for extending classes and consider utilizing interfaces with declaration merging for expanding type declarations. Another alternative would be to use type assertion (even though it may compromise type checks): (<any>project).test()

Answer №2

By cleverly combining decorator and mixin techniques, I managed to get everything up and running smoothly.

Start off by defining your own mixin.

PLEASE NOTE: While using a mixin is optional, it provides the advantage of multiple inheritance and increased flexibility.

export function CustomizeMixin<T extends CustomizeConstructor>({ Base }: MixinProps<T>) {

  class Mixin extends Base {

    // Additional decorators can be defined here
    async customMethod() {
      // Your implementation goes here
    }

  }

  return Mixin;
}

// Specify the type of your constructor.
export type Constructor<T, Arguments extends unknown[] = any[]> = new (...arguments_: Arguments) => T;
export type CustomizeConstructor<T = { }> = Constructor<T>;

// Define an interface with the same name as your decorator for proper declaration merging.
export interface CustomizeDecorator { 
  async customMethod(): Promise<void>;
}

// Implement your decorator
export function CustomizeDecorator(options = {}) {

  return function <T extends CustomizeConstructor>(Base: T) {

    // Additional decorators can be defined here
    class CustomizeClass extends CustomizeMixin(Base) { }

    // Transfer original class descriptors to the new class
    const ownPropertyDescriptors = Object.getOwnPropertyDescriptors(Base);

    const { prototype, ...descriptors } = ownPropertyDescriptors;

    Object.defineProperties(CustomizeClass, descriptors);

    return CustomizeClass as T;

  }

}

// Apply the decorator and declare a class interface that extends the decorator interface
// for efficient type merging.
interface CustomService extends CustomizeDecorator {}
@CustomizeDecorator({})
class CustomService {

}

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

Leveraging the power of mongodb bulkWrite in your nodejs/typescript

I'm working on a bulk update in MongoDB using the code snippet below: async function main() { try { const operations:any = [] users.forEach(async user => { const custId = decrypt(user.id) const customer = await CustomerModel.f ...

Exclusive Vue3 Props that cannot be used together

How can a component be created that accepts either json with jsonParserRules or jsonUrl with jsonParserRulesUrl, but not both? It would be ideal if the IDE could provide a warning when both props are specified. Example of an Attempt that does not Work < ...

From TypeScript to Java: transforming enums to JSON and back

As we develop an Angular2 app (utilizing Angular 13 and Typescript 4.5.5), we heavily rely on string enums, structured like so: export enum Language { de = "German", fr = "French", it = "Italian", ...

What is the best way to set a value to a decorated property within the constructor of a parent class

I am facing an issue with initializing a decorated property "name" in a User class within the parent class constructor (Base) using Object.assign. The value ends up being "undefined" when the decorator is present, but it works correctly when the decorator ...

Do I have to wait for the HTTP get request to access the fetched object property?

I am currently working with Angular and TypeScript on a dish-detail component that is accessed through 'dishes/:id' The dish object returned has a property called components, which contains an array of objects with two properties: id: type stri ...

Describe a category of alternating couples

After coming across the query on How to define array with alternating types in TypeScript?, I discovered that it is indeed possible to specify a type of array where the elements must follow a specific order. For instance, the following sequences are consid ...

Creating a carousel with material design aesthetics

I'm working on implementing a carousel in my setup using Angular CLI: 6.0.5, Node: 10.1.0, OS: win32 x64, and Angular: 6.0.3. However, I haven't been able to locate documentation for creating the carousel in Angular's Material Design framewo ...

Modify the selected toggle buttons' color by utilizing the MUI ThemeProvider

I am currently working on customizing the color of selected toggle buttons within my React app using TypeScript and ThemeProvider from @mui/material 5.11.13. Despite my efforts, when a toggle button is selected, it still retains the default color #1976d2, ...

Customizing TinyMCE's font style menu options

Our platform utilizes TinyMCE as in-place editors to allow users to make live edits to content. However, a challenge arises when using a dark background with light text, as TinyMCE defaults to using this text color rather than black. https://i.sstatic.net ...

What is the correct way to handle errors in TypeScript when using Axios?

I've got this code snippet that's currently working: try { self.loadingFrameMarkup = true; const { data }: AxiosResponse<IMarkupFrameData> = yield axios.post<IMarkupFrameData>( ...

Retrieving information from the Dog API using axios and storing the results in a fresh array

Currently, I am working on a NextJS app using Typescript. My issue lies in the functionality aspect of the application. I am utilizing the Dog API to retrieve all the breeds names and store them in a new array of arrays. Each sub-array contains the breed a ...

The react-bootstrap module does not have any members available for export

I'm looking to incorporate the npm module react-bootstrap from this link into my Layout component, following the ASP.NET Core 2.1 template client project structure. The challenge I'm facing is that the template uses a .js file extension, but I pr ...

What is preventing me from being able to spyOn() specific functions within an injected service?

Currently, I am in the process of testing a component that involves calling multiple services. To simulate fake function calls, I have been injecting services and utilizing spyOn(). However, I encountered an issue where calling a specific function on one ...

Remove httpOnly cookies in Express

Can browser cookies with the attribute HttpOnly:true be deleted? Here is a snippet of my login endpoint: async login(@Ip() ipAddress, @Request() req, @Res() res: Response) { const auth = await this.basicAuthService.login(req.user, ipAddress); ...

Learn how to creatively style buttons with dynamic effects using tailwindcss

My Desired Button: I have a Button component that can accept a variant prop. My goal is to have the button's className change dynamically based on the prop passed to it. Instead of using if/else statements for different buttons, I want to use a sing ...

What is the most effective method of utilizing union or extend in TypeScript when faced with a comparable scenario?

I have a function that takes in two different types of objects: const canBeGenericOrDefaultData = { id: 123, pointData: { square: 'x145', triangle: 'y145' } } function submitHandler(canBeGenericOrDefaultData: AllTheDatas | G ...

The specified argument, 'void', cannot be assigned to a parameter that expects 'SetStateAction | undefined'

Currently, I am engaged in a TypeScript project where I am fetching data from an endpoint. The issue arises when I attempt to assign the retrieved data to my state variable nft using the useState hook's setNft function. An error is being thrown specif ...

Exploring the depths of friendship with React-Router V6 through recursive routes

I am currently facing an issue with my React-Router V6 implementation. The example I found for recursive routes in React-Router V5 is exactly what I need: However, after migrating to react-router-dom@6, the output is not as expected. import { Routes, ...

NavigatedRoute and Auth-protect - problem retrieving ID from paramMap

Currently working on a website for my exam project, but encountering an issue with the AuthGuard not returning the correct ID in my code. event-details.component.ts getEvent(): void { const id = +this.route.snapshot.paramMap.get('id'); ...

Ways to eliminate Typescript assert during the execution of npm run build?

How can I effectively remove Typescript asserts to ensure that a production build generated through the use of npm run build is free of assertions? Your assistance is appreciated ...