Employ a class decorator to modify methods within a subclass

Is there a way to utilize class decorators in order to modify the methods of subclasses for the decorated class? This particular example showcases how to alter the class's own methods, but does not extend to its subclasses:

export function guardAllNonConstructorOwnMethodsAgainstBadArguments(
  constructor: Function
) {
  const badArguments = new Set(['', undefined, null]);
  const prototype = constructor.prototype;
  Object.getOwnPropertyNames(prototype)
    .filter(
      ownPropertyName => typeof prototype[ownPropertyName] === 'function' && ownPropertyName !== 'constructor'
    )
    .forEach(propertyName => {
      const nonConstructorOwnMethod = prototype[propertyName];
      prototype[propertyName] = function(...args: any[]) {
        const everyArgumentIsGood = args.every(arg => !badArguments.has(arg));
        if (everyArgumentIsGood) {
          return nonConstructorOwnMethod.bind(this)(...args);
        }
      };
    });
}

So, with this specific scenario in mind, how could one adapt this decorator to also safeguard the methods of the subclasses belonging to the class where this decorator is used against "bad arguments"?

Answer №1

Class decorators are invoked only once, during the class definition process. They do not affect subclasses directly. To decorate subclasses, you would need to individually apply the decorator to each subclass.

Currently, there are no universal triggers available to automatically execute code when a subclass extends an already decorated superclass. One workaround could involve having subclasses invoke your code when their first instance is created. This means that while

class Subclass extends Superclass {}
doesn't trigger any action, calling new Subclass() will activate the code, but subsequent instances won't. It may suffice for some scenarios. Here's one possible implementation:

const handleForThisClassAndForEachSubclass =
    (cb: ((ctor: new (...args: any) => void) => void)) =>
        (ctor: new (...args: any) => any) => {
            const registry = new Set<new (...args: any) => any>();
            const alreadyDecorated = Symbol();
            const {[ctor.name]: newCtor} = {
                [ctor.name]: class extends ctor {
                    constructor(...args: any) {
                        super(...args);
                        const thisCtor = this.constructor as new (...args: any) => any;
                        if (!registry.has(thisCtor)) {
                            cb(thisCtor);
                            registry.add(thisCtor);
                        }
                    }
                }
            };
            cb(newCtor);
            registry.add(newCtor);
            return newCtor;
        };

This creates a registry of constructors and calls the callback function just once for each encountered constructor. Overriding the superclass constructor ensures that actions are triggered when constructing subclasses. While the approach may seem complex, its functionality can be demonstrated as follows:

const show = (ctor: new (...args: any) => any) => {
    console.log("I'm customizing this", ctor);
}

console.log("before declaring superclass")  
@handleForThisClassAndForEachSubclass(show)
class Superclass {

} // I'm customizing this function Superclass()
new Superclass(); // no output

console.log("before declaring subclass")
class Subclass extends Superclass {
} // no output

console.log("before creating subclass instance")
new Subclass(); // I'm customizing this function Subclass()
console.log("before creating subclass instance")
new Subclass(); // no output

console.log("before declaring another subclass")
class Subclass2 extends Superclass {
} // no output

console.log("before creating another subclass instance")
new Subclass2(); // I'm customizing this function Subclass2()
console.log("before creating another subclass instance")
new Subclass2(); // no output

console.log("before subsubclass declaration")
class SubSubclass extends Subclass {
} // no output

console.log("before subsubclass creation")
new SubSubclass(); // I'm customizing this function SubSubclass2()
console.log("before subsubclass creation")
new SubSubclass(); // no output

The function show() demonstrates being called once for SuperClass at creation time and again for each subclass upon their initial instantiation. Depending on your specific requirements, substituting show with your intended function like

guardAllNonConstructorOwnMethodsAgainstBadArguments
might yield useful results. Regardless, this method of overriding superclass constructors can lead to customized actions when constructing subclass instances. Best of luck with your application!

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

Enhance the performance of your React/NextJS app by controlling the rendering of a component and focusing on updating

I'm facing an issue with my NextJS application that showcases a list of audio tracks using an <AudioTrackEntry> component. This component receives props like the Track Title and a Link to the audio file from an external data source. Within the ...

Implement a function for templateURL in AngularJS using Typescript programming language

Here is my current setup: export class MyComponent implements ng.IComponentOptions { public static componentName: string = "myViewer"; public bindings: any; public controller: any; public controllerAs: any; public templateUrl: string; ...

ERROR: The use of @xenova/transformers for importing requires ESM

When working with my Node.js application, I usually start by importing the necessary modules like this: import { AutoModel, AutoTokenizer } from '@xenova/transformers'; Afterwards, I utilize them in my code as shown below: const tokenizer = awai ...

The type 'undefined' cannot be assigned to type 'Element or null'

One of the components I am using looks like this: const data: any[] = [] <Tiers data={data}/> This is how my component is structured: const Tiers = ({ data, }: { data?: any; }) => { console.log('data', data?.length!); ...

Angular Pipe -- Implicit type 'any' error occurs when trying to index type 'string' with an expression

Encountering an error while constructing the time ago pipe: Obtaining an implicit 'any' type due to inability to index type with a 'string' expression if (value) { const seconds = Math.floor( (+new Date() - +new Date(Numb ...

Who is responsible for the addition of this wrapper to my code?

Issue with Sourcemaps in Angular 2 TypeScript App Currently, I am working on an Angular 2 app using TypeScript, and deploying it with the help of SystemJS and Gulp. The problem arises when I try to incorporate sourcemaps. When I use inline sourcemaps, eve ...

Understanding and processing HTML strings in typescript

I am currently utilizing TypeScript. Within my code, there is an object named "Reason" where all variables are defined as strings: value, display, dataType, and label. Reason = { value: '<ul><li>list item 1</li><li&g ...

Exploring the implementation of initiating paypal in NestJs using Jest testing framework

Currently, I am creating a test for a method within NestJs that is responsible for initiating a Paypal Payment intent. When I execute either the yarn test:watch or simply yarn test command, the test described below runs successfully and passes. However, up ...

synchronize the exchange of information and events between two components

Just joined this platform and diving into Angular 7 coding, but already facing an issue. I've set up two components along with a service. The data is fetched by the service upon clicking a button in the searchbar component, and I aim to display it i ...

Tips for utilizing generics to determine the data type of a property by its name

I am seeking a method to determine the type of any property within a class or a type. For instance, if I had the classes PersonClass and PersonType, and I wanted to retrieve the type of the nationalId property, I could achieve this using the following cod ...

Icon for closing Mui Snackbar

I am facing an issue with my notification component that uses the mui snackbar to display alerts. I want to display multiple notifications stacked vertically, but when I try to close one notification using the "Close" icon, it ends up closing both that o ...

Utilizing a TypeScript definition file (.d.ts) for typings in JavaScript code does not provide alerts for errors regarding primitive types

In my JavaScript component, I have a simple exporting statement: ./component/index.js : export const t = 'string value'; This component also has a TypeScript definition file: ./component/index.d.ts : export const t: number; A very basic Typ ...

What is the purpose of mapping through Object.keys(this) and accessing each property using this[key]?

After reviewing this method, I can't help but wonder why it uses Object.keys(this).map(key => (this as any)[key]). Is there any reason why Object.keys(this).indexOf(type) !== -1 wouldn't work just as well? /** * Checks if validation type is ...

Error: 'next' is not defined in the beforeRouteUpdate method

@Component({ mixins: [template], components: { Sidebar } }) export default class AppContentLayout extends Vue { @Prop({default: 'AppContent'}) title: string; @Watch('$route') beforeRouteUpdateHandler (to: Object, fro ...

The build error TS1036 is thrown when a function with a return statement is moved to index.d.ts, even though it worked perfectly in a standard TS file

In my project using Node v14.x and StencilJS (React) v2.3.x, I encountered an issue with a test-helper file containing a function that converts string-arrays to number-arrays: export function parseNumericStringOrStringArrayToIntegers(value: string | (strin ...

Preserve the timestamp of when the radio query was chosen

I'm interested in finding a way to save the user's selected answer for a radio button question and track the time they saved it. Is there a way to achieve this using HTML alone? Or would I need to utilize another coding language or package? Just ...

Setting a maximum limit for selections in MUI Autocomplete

Code updated to display the complete script logic. I want to restrict the number of selections based on a value retrieved from the database, but in this example, I have set it to 3 manually. The error message I'm encountering is "Cannot read properti ...

Is it possible to loop through a subset of a collection using *ngFor?

Is it possible to iterate through a specific range of elements in a collection using *ngFor? For instance, I have a group of checkboxes with their form control name and label specified as follows: [{id: 'c1', label: 'C1'}, ...] Assum ...

What are the solutions for resolving 'undefined' errors while working with TypeScript Interfaces?

When working with TypeScript in my Next.js project, I encountered the following error message: Type '{ banner: { id: number; bannerImageUrl: string; } | undefined; }' is not assignable to type 'IntrinsicAttributes & Banner'. Prope ...

Creating an interface for React props

Hey everyone, I'm facing an issue and need some advice. I prefer using interfaces to maintain readability and utilize intellisense in my code. However, I'm struggling with implementing this approach when working with data passed through props. H ...