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

Improve the way you manage the active selection of a button

ts isDayClicked: { [key: number]: boolean } = {}; constructor() { } setSelectedDay(day: string, index: number): void { switch (index) { case 0: this.isDayClicked[0] = true; this.isDayClicked[1] = false; this.isDay ...

Can you effectively leverage a prop interface in React Typescript by combining it with another prop?

Essentially, I am looking to create a dynamic connection between the line injectComponentProps: object and the prop interface of the injectComponent. For example, it is currently set as injectComponentProps: InjectedComponentProps, but I want this associat ...

Retrieving all records in Firestore that have access to their child documents

I'm attempting to retrieve all the documents from each child collection (ratings) and update the average rating in the foobar document. However, I am encountering an error in one of my callable functions: Unhandled error RangeError: Maximum call stack ...

Obtain a union type using the `keyof typeof` syntax

Is there a way to retrieve the union or enum type from a typeof type in TypeScript? For instance: const myConfs: { [k: string]: (myArg: { name: string }) => string } = { 'Hello': ({ name }) => `World from ${name}`, 'Goodbye': ...

Proper positioning of try/catch block in scenarios involving delayed async/await operations

For the past six months, I have been utilizing async/await and have truly enjoyed the convenience it provides. Typically, I adhere to the traditional usage like so: try { await doSomethingAsync() } catch (e) {} Lately, I've delved into experimenti ...

Exploring the Differences between Angular's Http Module and the Fetch API

While I grasp the process Angular uses for HTTP requests, I find myself leaning towards utilizing the Fetch API instead. It eliminates the need to subscribe and unsubscribe just for a single request, making it more straightforward. When I integrated it int ...

Ways to Prompt a User to Select the "Remember Me" Option

How can I implement the functionality of 'Remember Me' on a login page? I want users who click on 'Remember Me' to be able to reopen the page without logging in again, even after closing their browser. But how do I differentiate between ...

Utilize Function type while preserving generics

Is there a way in Typescript to reference a function type with generics without instantiating them, but rather passing them to be instantiated when the function is called? For instance, consider the following type: type FetchPageData<T> = (client : ...

Sinon Stub generates varying values with each invocation

I'm pretty new to TypeScript and JavaScript, but I've managed to create a functioning VScode extension that I'm really happy with. However, I'm running into some issues with my Mocha tests. Here's a snippet of the code I'm str ...

Dealing with incorrect type declarations in Typescript when using Material-UI Higher Order Components can

Currently, I am in the process of upgrading from Material-UI 1.x to the newer version Material-UI 3.9.2. I had several components that were functioning well with Higher Order Components (HOC), but when it comes to migrating them to 3.9.2, I am facing some ...

The Expo TypeScript template highlights JSX errors such as "Cannot assign type 'boolean' to type 'View'. TypeScript error 2322 at line 5:10:5"

Just starting out with Expo and decided to dive in with the typescript template using the npx create-expo-app -t expo-template-blank-typescript command. However, I'm running into some JSX type errors that are popping up even though the Expo server see ...

What is the best way to specify Next.js Context types in TypeScript?

Can someone help me with defining the types for next js Context and req? Below is the code for the getServerSideProps function- //Server side functions export const getServerSideProps: GetServerSideProps = async (context) => { await getMovies(conte ...

What is the correct way to submit a formarray in an angular application following the specified format?

When submitting a form in Angular, I'm facing an issue where only the data from the first index inside the role menu is being passed. How can I ensure that all index data is passed on submit? { "roleMenu":[{ "menu":{ "menuId": 1 }, ...

Anonymous function bundle where the imported namespace is undefined

Here is the code snippet I am working with: import * as Phaser from 'phaser'; new Phaser.Game({ width:300, height:300, scale: { mode: Phaser.Scale.FIT, }, type: Phaser.AUTO, scene: { create() {} }, }); Upon compi ...

The type '{ children: Element; }' cannot be assigned to the type 'IntrinsicAttributes & ReactNode'

Encountered this error: Type '{ children: Element; }' is not assignable to type 'IntrinsicAttributes & ReactNode'. export const withAppProvider = (Component: AppComponent) => { return function WrapperComponent(props: any) { ...

Using Angular to Apply a Custom Validation Condition on a FormGroup Nested Within Another FormGroup

I am facing an issue with my form validation logic. I have a set of checkboxes that need to be validated only when a specific value is selected from a dropdown. The current validator checks the checkboxes regardless of the dropdown value. Here's the c ...

TypeScript integration for express-validator

Recently, I made an attempt to switch my NodeJS project with ExpressJS to TypeScript for better organization and type safety. However, I encountered an issue with the 'express-validator' middleware during this conversion process. To resolve thi ...

How can I retrieve all element attributes in TypeScript using Protractor?

I came across this solution for fetching all attributes using protractor: Get all element attributes using protractor However, I am working with TypeScript and Angular 6 and struggling to implement the above method. This is what I have tried so far: im ...

Encountering a Problem with HTTP Requests in Angular 2

Seeking assistance with a technical issue. My objective: Make a REST API call to retrieve JSON data and resolve an Angular 2 promise. ServerAPI built with Node.js/ExpressJS/Lodash Sample of server.js file: var express = require('express'); va ...

Accessing information necessitates two separate subscriptions

I am posting this inquiry in order to enhance my understanding. Below is an excerpt from my service: export class HomeService { private generalstatistics = new ReplaySubject<object>(); constructor( private http: HttpClient ) { this ...