Typed method decorations in TypeScript provide robust type checking and enforcement in your codebase

I am currently working with TypeScript code that involves listening to events based on topics generated from specific contexts. I am looking to streamline the event registration process by utilizing method decorators.

For instance, I have a "Controller" class that provides a context for topic generation in event registration and an "@action" decorator that takes this context and returns a topic.

interface ContextProvider<T> {
  getContext(): T;
}

type MyContext = {
  some_id: number;
};

class Controller implements ContextProvider<MyContext> {
  @action(ctx => `event:${ctx.some_id}`)
  public eventHandler(message: any) {
    console.log("My event handler", message);
  }

  getContext(): MyContext {
    return { some_id: 6 };
  }
}

I have created a simple decorator -

function action<TContext>(handler: (TContext) => void): Function {
  return function (object: object, aub, descriptor: any) {

    if(!object.hasOwnProperty("getContext")) {
      throw new Error("Can't bind @action to a class that does not implement the 'getContext' method");
    }

    // @ts-ignore
    const ctx = object.getContext();

    console.log(`listening to: ${handler(ctx)}`)
  };
}

While it currently works, my concern lies with maintaining type-safety -

  • ctx inside the handler is of type "any" instead of MyContext - although I have a unit test to verify the correct topic generation, I am seeking better auto-completion and compile-time error detection.
  • I can mistakenly use the @action decorator on a class that does not implement the ContextProvider interface which results in runtime errors; I aim to catch such issues at compile time.

Upon observation, it appears there is no straightforward solution to these problems, leading me to consider resorting to repeated code like:

@action<MyContext>(ctx => `hello:${ctx.some_id}`)

Do you have any suggestions on how to address these challenges?

Answer №1


type MyContext = {
  userID: number;
};

// additional constraint
function performAction<TContext extends MyContext>(handler: (arg: TContext) => void): Function {
  return function (object: ContextProvider<MyContext>, aub: any, descriptor: any) {

    if (!object.hasOwnProperty("proceedWithContext")) {
      throw new Error("Unable to bind @performAction to a class that does not implement 'proceedWithContext' method");
    }

    // @ts-ignore
    const ctx = object.proceedWithContext();

    console.log(`listening on: ${handler(ctx)}`)
  };
}

interface ContextProvider<T> {
  proceedWithContext(): T;
}


class ProcessController implements ContextProvider<MyContext> {
  @performAction(ctx => `event:${ctx.userID}`)
  public eventHandler(message: any) {
    console.log("Event handled successfully", message);
  }

  proceedWithContext(): MyContext {
    return { userID: 6 };
  }
}

// TESTING


class Controller2 {
  @performAction(ctx => `event:${ctx.userID}`) // error
  public eventHandler(message: any) {
    console.log("Event handler encountered an issue", message);
  }

}

Simply apply an extra generic constraint for

performAction<TContext extends MyContext

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

What steps are necessary to ensure that this validation is functional when set as a required

I've been trying to implement validation in my app, but I'm facing some issues with getting it to work correctly. My goal is to display a message "email is required" when the user leaves the email input empty (even if they make changes), and show ...

Can anyone clarify what the term 'this' is referring to in this particular snippet of code?

Upon analyzing the following code snippet: export abstract class CustomError extends Error { abstract statusCode: number; constructor(message: string) { super(message); Object.setPrototypeOf(this, CustomError.prototype); } abstract seri ...

Angular Routing Error: 'No routes matched'

Just completed the setup of a new Angular project and added two Component Routes in my AppRouting Module. Encountering an error when attempting to access a route: ERROR Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'cr-c ...

Encountering a problem with loading popper.js in Bootstrap 4 while utilizing require.js for loading

After installing bootstrap 4, jquery, and popper via npm, I encountered an error in the console when loading my website. The error indicated that the file popper.js could not be retrieved. Upon further inspection, I found that there were two instances of p ...

How can I remove specific items from a PrimeNG PickList?

Currently, I'm working on a page where updates are made using PrimeNG PickList. The initial state of the target list is not empty, but when selected items are moved from source to target list, they are not removed from the source list as expected. Fr ...

Errors in Javascript unit testing: despite being stubbed, functions are still being activated

I have encountered issues while writing unit tests, and I am currently facing errors that I am trying to troubleshoot. The specific unit test concerns the index.ts file, which calls the features/index.ts file. To simulate the default export from features/ ...

An optional field has been identified as ng-invalid

In my set-up, I have created a form group using reactive forms. this.transactionForm = fb.group ({ 'location': [null, Validators.required], 'shopper': [null], 'giftMessage': [null], 'retailPrice& ...

Trouble arises when extending an MUI component due to a TypeScript error indicating a missing 'css' property

We have enhanced the SnackbarContent component by creating our own custom one called MySnackbarContent: export interface MySnackbarContentProps extends Omit<SnackbarContentProps, 'variant'> { variant?: MyCustomVariant; type?: MyCustomTy ...

Tips for including extra space before or after '{' and '}' in WebStorm?

While I have some experience with JetBrains products and setting styles for my files, I am struggling to configure my .tsx file to display the desired style. It appears that changes made in the TypeScript section are not affecting my .tsx file. In the ima ...

RXjs: Reverting a value to its original state after a specified duration

Within this service/state: export class SpinnerService { public throttleTime: number = 10; public isLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); constructor() {} public showLoader(): void { this.isLoad ...

Typescript: The function parameter in a class method is more specific than in the superclass

I am facing an issue with two TypeScript classes where one extends the other: type NamedObject = { name: string; } class AnyObjectManager { objectList = []; getAnyObject = (matches: (o: object) => boolean) => { for (const o of t ...

Angular real-time data tracker

As a newcomer to Angular, I am facing some challenges trying to achieve real-time data updates in my app. My goal is to retrieve JSON data from an MSSQL server via a web server. I have successfully fetched data using the following code: export class AppC ...

When executing the command "pnpm run tsc," an error message pops up saying "ERR_PNPM_NO_SCRIPT: Script 'tsc' is missing."

When attempting to run pnpm run tsc, an error is thrown: ERR_PNPM_NO_SCRIPT  Missing script: tsc Any suggestions on how to resolve this issue would be greatly appreciated. Thank you in advance. ...

Problems Encountered When Converting a Function to TypeScript

I've been trying to convert this function to TypeScript, but I've encountered some issues: import { Children, ReactNode } from 'react'; interface ChildType { role?: string; } export default function filterChildrenByRole( children: ...

Encountering a surprise focus error in ngui-auto-complete within Angular

In the process of developing a web application, I have encountered an unexpected focus issue with the ngui-auto-complete on one of the pages. Despite not setting any focus event for this particular element, it remains focused once the content is initialize ...

Tips for utilizing <Omit> and generic types effectively in TypeScript?

I'm currently working on refining a service layer in an API with TypeScript by utilizing a base Data Transfer Object. To prevent the need for repetitive typing, I have decided to make use of the <Omit> utility. However, this has led to some per ...

Can child directives in Angular 2 harness the power of parent providers?

I am facing an issue while trying to utilize a service as a provider for general use in a Directive rather than a Component. The problem arises when the service is not being received in the child Directive, despite my expectation to use it within the direc ...

Prevent redundant server responses when querying with Apollo and Angular

Below, you will find an example of the code I am currently using to submit data to a server and the resulting response that is received and saved: this.apollo.mutate( { mutation: XXXXXXXX, variables: { instance_string: X, accesstoken: X } }) .subscribe({ ...

React Alert: It is important for every child within a list to have a distinct "key" prop, not due to a missing key in the map

My current project involves developing a React 18 application. While working on one of the pages, I encountered the following error: https://i.sstatic.net/9Mk2r.png I am aware that this type of error is often linked to the absence of a unique key in the m ...

Troubleshooting the ineffectiveness of Relative paths, baseUrl, and paths in Ionic2 with Angular2

I've been doing some research on similar forums but I haven't been able to find a solution yet. There must be a small step that I am overlooking. My objective is to achieve: import { Logger } from 'logging' instead of import { Logg ...