Enhancing class functionality with decorators in TypeScript

Over on the TypeScript's Decorator reference page, there is a code snippet showcasing how to override a constructor with a class decorator:

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

Which logs the following:

class_1 {
  property: 'property',
  hello: 'override',
  newProperty: 'new property' }

All seems fine so far. However, attempting to access newProperty using dot notation results in the error message:

Property 'newProperty' does not exist on type 'Greeter'.ts(2339)

The error is not listed in hints from VS Code and accessing it with bracket notation prompts the warning:

Element implicitly has an 'any' type because type 'Greeter' has no index signature.ts(7017)

Is something missing here? How can one implement adding new properties via Decorators in a type-safe manner? The goal is to have proper compiler support similar to regular class members.

Answer №1

When it comes to decorators, they are unable to change the class type by design. Currently, there is an ongoing discussion about this limitation, and it seems that the behavior will remain unchanged until the decorator proposal is finalized. A possible alternative for achieving similar functionality is through the use of mixins (you can learn more about mixins in TypeScript here)

If we were to implement the above code using mixins, it would resemble something like this:

function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

const Greeter = classDecorator(class {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
});
type Greeter = InstanceType<typeof Greeter> // obtain the instance type just as if we had declared a class

console.log(new Greeter("world").newProperty);

Answer №2

function addNewProperty<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        extraProperty = "new addition";
        greet = "override";
    }
}
interface additionalProperties {
    newProperty: string;
    greet: string;
}

//trick
interface Greeting extends additionalProperties { };

@addNewProperty
class Greeting {
    property = "existing property";
    greet: string;
    constructor(message: string) {
        this.greet = message;
    }
}
const instance = new Greeting();
console.log(instance.extraProperty);

Utilizing the interface trick seems to be a viable solution for the current issue. For more details on this approach, refer to:

Answer №3

Here's a clever workaround for dealing with the limitations of decorators: Instead of using decorators directly, you can create a plain class that will properly inherit types.

class CustomClass {
        newProperty = "new property";
        greeting = "override";
}

class Person extends CustomClass {
    property = "property";
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
}

console.log(new Person("Hello, world!"));

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

NextJS compilation sometimes results in undefined errors when using Sass styles

My peace lies in the sass code: .link display: inline-table text-align: center align-self: center margin: 5px 15px font-size: 20px color: black text-decoration: none transition: 0.1s ease-in-out .link:hover text-decoration: underline .l ...

Issue with Angular2: The [routerLinkActive] directive does not update when using _router.navigate

My app includes several routerLinks that I have styled using [routerLinkActive]="['active']". Everything works perfectly when I click on one of the routerLinks to navigate. However, when I try to navigate using: this._router.navigate( [ thisUrl ...

Leveraging Angular Firebase MatTable with the power of 2 observables in 1

I'm currently facing an issue with my Firebase database data structure where I have a reference to a user id. Here's an example of the original data in my collection: { city: new york, country: usa addedBy: feibf78UYV3e43 // This is the USER ID ...

Angular rxjs Distinctions

Coming from AngularJS to Angular, I'm still trying to wrap my head around rxjs observable. For example: User.ts export class User { id?:any; username:string; password:string; } Using <User[]> myUser(header: any) { const url = `${this.mainUr ...

angular2 ngif does not effectively conceal HTML elements when set to false

In the HTML file, I have the following code: <p *ngIf="!checklistsready"> not ready </p> <p *ngIf="checklistsready"> Ready </p> And in my TypeScript file, it looks like this: checklistsready: boolean = false; constructor( ...

It is advisable for the subscriber not to halt upon encountering an error within the

Dealing with a problematic subscriber that automatically unsubscribes itself when an error occurs: observable .subscribe( (data) => { // logic encountering an error // similar to throw new Error() } ) To avoid this, I can use t ...

The ngxpagination template is currently experiencing issues with filtering and pagination functionality

I have encountered an issue with my custom pagination template using ngx-pagination. Everything works fine until I introduce a filter pipe alongside the pagination. The problem arises when the filtered data set is not properly paginated - instead of displa ...

Tips for navigating to a specific item in a react native list?

Is it possible to scroll to a specific element within a list based on another element in the list? For example, if you have a list [a,b,c,d], and each element is a touchableopacity with text a b c d respectively, can you set it up so that clicking on &apos ...

When making a variable call outside of a subscriber function, the returned value is 'undefined'

I find myself in a situation where I have to assign a value to a variable inside a subscriber function in Angular. The issue is that the variable returns 'undefined' when called outside of the Subscribe function. Here's what I'm encount ...

Attempting to transfer files to and from Firebase storage

Having trouble with my React Native app. I am trying to upload files, whether they are pictures or PDFs, but once uploaded, I can't seem to open them. However, "The files are getting uploaded to the storage." export const uploadToStorage = async (docu ...

Develop an rxjs pipeline that merges values according to their type prior to executing them in an async manner using concatMap

In my code, there's an eventStream that deals with different types of events and sends them to the server via HTTP. import { from, Observable } from 'rxjs'; import { concatMap } from 'rxjs/operators'; type Update = number[]; inte ...

Guide to making a Typescript type guard for a ReactElement type

I'm currently working with three TypeScript type guards: const verifyTeaserOne = (teaser: Teaser): teaser is TeaserOneType => typeof teaser === 'object' && teaser.type.includes('One'); const validateTeaserTwo = ( ...

Are all components in Next.js considered client components by default?

I have created a Next.js app using the app folder and integrated the Next Auth library. To ensure that each page has access to the session, I decided to wrap the entire application in a SessionProvider. However, this led to the necessity of adding the &apo ...

I am encountering an issue regarding the 'endpoint' property within my environment.ts file while working on an Angular 17 project

My goal is to incorporate the property endpoint from my environment.ts file into my service: export const environment = { production: false, endpoint: 'http://localhost:3000/api/cabin/' }; This snippet showcases my service: import {Injectabl ...

Verify if a given string exists within a defined ENUM in typescript

I have an enum called "Languages" with different language codes such as nl, fr, en, and de. export enum Languages { nl = 1, fr = 2, en = 3, de = 4 } Additionally, I have a constant variable named "language" assigned the value 'de'. My g ...

Compilation error in VueJS: missing dependency detected

I am facing an issue in my VueJS project where a file I am referencing seems to be causing a compilation error. Despite being present in the node_modules directory, the dependency is declared as not found. In the image on the left, you can see the directo ...

What is the best way to retrieve the file path of a imported Angular module?

I am trying to figure out how to retrieve the path of the BarComponent within the code snippet below. Specifically, I need to obtain '../bar/bar.component'. When dealing with a module loaded from a package such as Component module, I would like t ...

How can I use the target type (and maybe even the property type) as a type parameter within a decorator?

In the process of incorporating a deep-watch property decorator in Angular, the following usage has been implemented: @Component({ /* ... */ }) export class AppComp { @Watch( 'a.b.c', function (last, current, firstChange) { // ca ...

Error: the variable is not defined in the "onclick" event

I'm in the process of creating several buttons, each linked to a distinct modal element. I want to achieve this by using their id. However, I'm facing difficulties when trying to reference the variable from the typescript file. Although I don&ap ...

ReactJS: A single digit input may result in the display of numerous '0's

My goal is to have a small box that only allows one digit, but it seems to work fine until I try to input multiple '0's. Then the box displays multiple 0000 persistently. Here is the code snippet: const InputBox = () => { const [value, ...