Is it possible to dynamically incorporate methods into a class while specifying their types?

Can the def method be enhanced for type safety without needing to manually define interfaces?

// Can we add a proper `Cat.meow(message: string)` declaration on `Cat`?
const Cat = function() {}
def(Cat, {
  meow: function(message: string) { console.log('meow ' + message) },
})

const cat = new Cat()
cat.meow('I want food')

// Implementation
function def<T>(klass: T, fns: { [k: string]: any }) {
  for (let k in fns) {
    Object.defineProperty(klass.prototype, k, { value: fns[k],
      enumerable: false, writable: true, configurable: true
    })
  }
}

P.S. Perhaps using some Babel AST transform magic for TypeScript could help?

Answer №1

Instead of mutating the type of its klass argument, a cleaner concept would be to have def() return the desired constructor. However, it is still possible to achieve this by creating an assertion function that narrows the type of the klass argument to one with an appropriate construct signature.

One approach could look something like this:

function def<A extends any[], T extends object, M extends Record<keyof M, Function>>(
    klass: (this: T, ...args: A) => void, fns: M & ThisType<T & M>
): asserts klass is typeof klass & (new (...args: A) => T & M) {
    for (let k in fns) {
        Object.defineProperty(klass.prototype, k, {
            value: fns[k],
            enumerable: false, writable: true, configurable: true
        })
    }
}

This allows for klass to have an argument list of some generic type A, and aims to provide a useful experience with this inside both the klass and fns properties by leveraging a this parameter on klass and the magic ThisType<T> utility type on fns. The goal is to make whatever this refers to in

klass</code and whatever type <code>fns
is accessible as this within the argument to fns.

In practice, code following this pattern compiles and functions as intended:

const Cat = function () { }
def(Cat, {
    meow(message: string) { console.log('meow ' + message) },
})
const cat = new Cat()
cat.meow('I want food') // meow I want food

const Dog = function (this: { name: string }, name: string) {
    this.name = name;
}
def(Dog, {
    bark() { console.log(this.name + " barks") }
})
const dog = new Dog("Fido");
console.log(dog.name); // Fido
dog.bark(); // Fido barks

Link to 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

Exploring the process of dynamically incorporating headers into requests within react-admin

Currently utilizing react-admin with a data provider of simpleRestProvider. I am in need of a solution to dynamically add headers to requests based on user interactions. Is there a way to achieve this? Appreciate any assistance. Thank you! ...

Retrieve a specific subset of a union based on the class in a generic function

This question shares similarities with another post I made, but this time focusing on using classes instead of plain objects. class Exception1 extends Error { constructor(message: string, public arg1: string) { super(message); } } class Ex ...

Autodesk Forge serves as a hub for hosting dependencies on a nearby server

Our company has implemented an Angular/TypeScript version of the Forge Viewer, currently loading dependencies from Autodesk's server. To ensure these files are always accessible to our customers, we want to host them on our own servers. However, attem ...

Having difficulty deciphering the legend in the Highcharts library for Angular (angular-highcharts)

I have a requirement to display two datasets as dual column charts. (2) [{…}, {…}] 0: historyDate: "2021-02-10T10:00:000Z" documentStatusHistory: CANCELLED: 6 COMPLETED: 52 IN_PROGRESS: 1 OPEN: 1 ...

Establish a connection between a React variable and state management

In my codebase, I have an external module file named Task.ts. It contains the following: const taskList: Task[] = []; Class Task { ... } export { Task, taskList } The taskList is a list of Task objects that can be modified by the Task class. Now, i ...

Transfer information between different classes and proceed to loop through the updated class directly from the html webpage

The Scenario In my angular 7 application, I am fetching data from a web API in JSON format. The app is functioning correctly, but I believe I am making excessive API calls and can optimize it to just one call. Custom Class: export class customClass ...

The function is trying to access a property that has not been defined, resulting in

Here is a sample code that illustrates the concept I'm working on. Click here to run this code. An error occurred: "Cannot read property 'myValue' of undefined" class Foo { myValue = 'test123'; boo: Boo; constructor(b ...

Can one obtain a public IP address using Typescript without relying on third-party links?

Though this question has been asked before, I am currently working on an Angular 4 application where I need to retrieve the public IP address of the user's system. I have searched on Stackoverflow for references, but most posts suggest consuming a th ...

Having trouble retrieving values from radio buttons in Angular 2 forms

Having trouble displaying the values of radio button inputs in Angular 2 forms. ...

How come the type checker is not throwing an error for this indexable type?

I recently delved into the Microsoft Typescript Handbook and found myself intrigued by the indexable types chapter. To gain a deeper understanding, I decided to experiment with the code provided. Strangely enough, upon running this particular piece of code ...

Transferring Files from Bower to Library Directory in ASP.Net Core Web Application

Exploring ASP.Net Core + NPM for the first time, I have been trying out different online tutorials. However, most of them don't seem to work completely as expected, including the current one that I am working on. I'm facing an issue where Bower ...

Challenges in integrating a PrimeNG Angular2 component with DynamicDialogRef, as well as the difficulties encountered when attempting to do

I am currently working on implementing a component that utilizes dynamic dialog and requires a direct usage. In the DynamicDialog example, there is a constructor in the car demo list component. constructor(private carService: CarService, public ref: Dynami ...

Struggling to deploy a Typescript React / NestJS application on Heroku due to the error message "error TS2307: Cannot find module"?

Switching from a Typescript React/Express app to using Nest.JS has caused complications when deploying to Heroku. The app runs smoothly locally, but encounters build failures on Heroku. Despite efforts to troubleshoot, it remains unclear whether the issues ...

Is it normal for TypeScript to not throw an error when different data types are used for function parameters?

function add(a:number, b:number):number { return a+b; } let mynumber:any = "50"; let result:number = add(mynumber, 5); console.log(result); Why does the console print "505" without throwing an error in the "add" function? If I had declared mynumber ...

Issue with incorrect inference of TextField `helperText` when using Formik with Material-UI

Upgrading to react-scripts 5 has caused an issue with the helperText on the TextField component. My TypeScript knowledge is not strong, so I'm having trouble fixing it. Here's more information about the problem: The error message: TS2322: Type & ...

A step-by-step guide on incorporating MarkerClusterer into a google-map-react component

I am looking to integrate MarkerClusterer into my Google Map using a library or component. Here is a snippet of my current code. Can anyone provide guidance on how I can achieve this with the google-map-react library? Thank you. const handleApiLoaded = ({ ...

Mongodb Dynamic Variable Matching

I am facing an issue with passing a dynamic BSON variable to match in MongoDB. Here is my attempted solutions: var query = "\"info.name\": \"ABC\""; and var query = { info: { name: "ABC" } } However, neither of thes ...

Having trouble appending a new attribute to the Mongoose output

In my Nodejs server application, I am working with a userDetail document that contains all the relevant user information. Additionally, I have a login document that stores the time of the first login, which I need to incorporate into the userDetails result ...

Distinguishing SharePoint React Webpart from Office: A Comparison

My goal is to develop a client-side Webpart in React for SharePoint Online that utilizes OfficeJs to initiate a new email message on the user's Outlook desktop. However, I'm encountering a persistent "Office is not defined" error no matter what ...

Preventing duplicate namespace declarations in TypeScript

Let's say I have a variety of objects with the following data structure: { namespace: 'first'|'second'|'third' } Now, I need to include another object with the same data structure, but its namespace cannot be assigned ...