Avoid saying the same thing more than once

Within my Typescript class, I have the following structure:

class C {
  #fsm
  (...)
  startFoo(name: string) {
    this.#fsm.send('FOO', name)
    return this
  }
  startBar(name: string) {
    this.#fsm.send('BAR', name)
    return this
  }
  (...)
}

While startFoo and startBar represent just a couple of examples, there are numerous similar functions in my codebase. I am exploring ways to avoid repetition. Can decorators help streamline this process?

Answer №1

To tackle this problem, we can start by introducing a new method called protected. This method will act as a delegate for all calls to functions like startFoo(), startBar(), and so on. Let's name this method startify() and have it accept an additional argument named type:

class C {
    #fsm = {
        send(type: string, name: string) {
            console.log("I was sent type \"" + type + "\" and name \"" + name + "\"");
        }
    }
    protected startify(type: string, name: string) {
        this.#fsm.send(type, name);
        return this;
    }
}

Next, we need to include the startXxx methods in the class prototype and inform the compiler about these methods available in class instances. Here's how we can achieve that:

const methodNames = ["foo", "bar", "baz"] as const;

The methodNames array consists of all lowercase names after start. By applying a const assertion, we maintain the literal types of the strings within the array.

type MethodNames = typeof methodNames[number];

The MethodNames represents the union of string literal types in methodNames, which in this case is "foo" | "bar" | "baz".

type CMethods = { [K in MethodNames as `start${Capitalize<K>}`]: { (name: string): C } };

In CMethods, keys are remapped from MethodNames with the initial letter capitalized using Capitalize<T> utility function. These mapped keys signify the method names ("startFoo", "startBar", "startBaz") while values represent methods that take a string argument and return a value of type C.

interface C extends CMethods { }

By incorporating declaration merging, each instance of C now encompasses all methods in CMethods along with those declared inside class C {}.

In conclusion, the compiler recognizes the added methods, but they need to be implemented. We create a utility function called capitalize() followed by dynamically adding methods to C.prototype for each element in

methodNames</code to delegate to <code>startify
using the call() method.

Testing our implementation:

const c = new C();
c.startBar("barf").startFoo("food")
// Output:
// I was sent type "BAR" and name "barf"
// I was sent type "FOO" and name "food"

Success! The compiler expects c.startBar() to exist and return a C for chaining calls. At runtime, the delegation to startify works seamlessly.

View code on TypeScript Playground

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

strange complications with importing TypeScript

In my Typescript projects, I frequently use an npm module called common-types (repository: https://github.com/lifegadget/common-types). Recently, I added an enum for managing Firebase projects named FirebaseEvent. Here is how it is defined: export enum Fi ...

reinstate dummy of external class method in ts-jest

Problem I am encountering an issue while trying to mock a function that is imported from another class and called within the main class. Although I can successfully mock the function and return the specified values, I am unable to revert the mocked functi ...

Is it possible to export all types/interfaces from .d.ts files within multiple folders using index.ts in a React TypeScript project?

In my current React project, I am managing multiple configuration folders: -config -api/ |index.ts |types.d.ts -routes/ |index.ts |types.d.ts ... For example, in the api/index.ts file, I can import necessary types using import {SomeTyp ...

Utilizing the Solana Wallet Key for ArweaveJS Transaction Signing

Is there a method to import a Solana wallet keypair into the JWKInterface according to the node_modules/arweave/node/lib/wallet.d.ts, and then generate an Arweave transaction using await arweave.createTransaction({ data }, jwk);? Metaplex utilizes an API ...

What exactly does the $any() type cast in Angular do and how is it used?

While browsing the Angular.io site, I came across this title: The $any() type cast function There are instances where a binding expression may trigger a type error during AOT compilation and it is not possible or difficult to fully specify the type. To re ...

How to iterate through properties declared in an Interface in Angular 12?

Before Angular 12, this functioned properly: export interface Content { categories: string[] concepts: Topic[] formulas: Topic[] guides: Topic[] } //this.content is of type Content ['formulas', 'concepts'].forEach(c =&g ...

Ionic: Automatically empty input field upon page rendering

I have an input field on my HTML page below: <ion-input type="text" (input)="getid($event.target.value)" autofocus="true" id="get_ticket_id"></ion-input> I would like this input field to be cleared eve ...

Unable to resolve all parameters for the RouterUtilities class

My goal is to develop a RouterUtilities class that extends Angular's Router. Despite the app running and compiling smoothly, when I run ng build --prod, it throws an error message like this: ERROR in : Can't resolve all parameters for RouterUtil ...

The sequence of execution in React hooks with Typescript

I'm having difficulty implementing a language switching feature. On the home page of my app located at /, it should retrieve a previously set preference from localStorage called 'preferredLanguage'. If no preference is found, it should defau ...

Creating a contravariant function member in TypeScript?

I am facing a challenge with a class that contains a member which is a function taking an instance of the same class: class Super { public member: (x: Super) => void = function(){} use() {const f = this.member; f(this)} } However, I need the me ...

What is the correct way to specify type hints for a Stream of Streams in highlandjs?

I'm currently working with typescript@2 and the highlandjs library. In the typings for highland, there seems to be a missing function called mergeWithLimit(n). This function: Accepts a Stream of Streams, merges their values and errors into a single ...

Discover how to capture and process POST form data in Angular originated from external sources

I am currently building a website that utilizes Java for the backend and Angular for the frontend. I have encountered a scenario where external websites may transmit data to my site via POST form submissions. For example, ▼ General    & ...

What sets React.HTMLProps<HTMLDivElement> apart from React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>?

Exploring the differences between interfaces and types in React: interface Properties1 extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {} interface Properties2 extends React.HTMLProps<HTMLDivElement> ...

Attempting to leverage the combination of mocha, ES6 modules, and ts-node while utilizing the --experimental-loader option

I've been attempting to make the ts-node option --experimental-loader function alongside mocha, but so far I haven't had any success. Before I started compiling ES6 modules, running mocha tests was as simple as: "test": "nyc --reporter=html mocha ...

Discover the most helpful keyboard shortcuts for Next.js 13!

If you're working with a standard Next.js 13 app that doesn't have the experimental app directory, setting up keyboard shortcuts can be done like this: import { useCallback, useEffect } from 'react'; export default function App() { c ...

Steps for storing API information in localStorage:1. Retrieve the API data

My app is running sluggish due to the excessive API calls for information retrieval. To optimize performance, I want to create a unified object containing all the data that can be shared across pages and accessed from localStorage, thus enhancing the app ...

Guide on utilizing external namespaces to define types in TypeScript and TSX

In my current project, I am working with scripts from Google and Facebook (as well as other external scripts like Intercom) in TypeScript by loading them through a script tag. However, I have encountered issues with most of them because I do not have acces ...

A Guide to Implementing Schema.virtual in TypeScript

After switching from using schema.virtual in JavaScript to TypeScript, I encountered an error when trying to use it with TypeScript. Below is my code: UserSchema.virtual('fullname').get(function () { return `${this.firstName} ${this.lastName}` ...

Angular - Acquire reference to the <audio> element

Is there a way to access the methods of an audio tag within my component in order to implement play and pause functions based on click events? The current method I tried does not allow me to access the play() function. How can I correctly approach this? ...

The confirm alert from Material UI is being obscured by the dialog

How can I ensure that a material ui dialog does not hide the alert behind it when confirming an action? Is there a way to adjust the z index of the alert so that it appears in front of the dialog? import Dialog from "@material-ui/core/Dialog"; i ...