Creating a generic constructor in TypeScript while working with inheritance is necessary for defining a flexible and reusable class

In my typescript project, I am looking to create a factory function that can generate a new object instance based on an existing object and a set of properties to populate it with. Essentially, I want to be able to convert one type of object into another related type. Here is an example of how I would like to use this function:

const newA = A.from({ a: 1 });
const newC = C.from({ a: 1, b: 2, c: 9 });

I have already implemented this functionality, but I believe there may be a simpler way using Object.create. However, the current implementation only works when inheriting from the base class, which is not ideal for me as I don't want to duplicate the function every time I create a new subclass.

class A {
  static from(data: Partial<A>): A {
    const o = Object.create(A);
    Object.assign(o, data);
    return o;
  }
}

I have tried to make the function more generic so that I can use it without specifying the class each time. While the following code snippet does work, I still need to explicitly define the class when calling the function:

class A {
  ...
  static from<T extends A>(data: Partial<T>): T {
    const o = Object.create(this);
    Object.assign(o, data);
    return o;
  }
}

const newC = C.from<C>({ ... });

I was hoping to find a way to incorporate generics in a better way, something like this:

class A {
  static from<T extends this>(data: Partial<T>): A {
    const o = Object.create(this);
    Object.assign(o, data);
    return o;
  }
}

const new C = C.from({ ... });

I believe there should be a way to achieve this using inferred class types during compile time, but I haven't been able to figure out the correct syntax. Is this approach considered acceptable or are there better alternatives?

Any guidance on this matter would be greatly appreciated!

Answer №1

If you're searching for polymorphic `this` types for static methods in TypeScript, unfortunately they are not currently supported. There is a feature request open for it at microsoft/TypeScript#5863, but its implementation remains uncertain.

However, there are workarounds available. One approach is to use a generic method where the `this` parameter depends on the generic. For instance:

class A {
    static from<T extends A>(this: new () => T, data: Partial<T>): T {
        const o = new this(); // no-arg constructor
        Object.assign(o, data);
        return o;
    }
}

In this example, only objects which are no-argument constructors of some subtype of `A` can call `from()`, and they will return that particular subtype. By changing the implementation from `Object.create(this)` to `new this()`, we emphasize the importance of an argument-free constructor (as a subclass invoked with `Object.create(this)` requiring a constructor argument may fail to create a valid instance).

Here's how it works - since `B` is a no-argument constructor of `B` instances:

class B extends A {
    foo = "bar";
}
const b = B.from({}); // B
console.log(b.foo.toUpperCase()); // BAR

Conversely, if `Z` is not a no-argument constructor of `Z` instances, the following code snippet will fail:

class Z extends A {
    constructor(public bar: string) {
        super();
    }
}
const z = Z.from({}); // error!
console.log(z.bar.toUpperCase());  // runtime error, z.bar is undefined

I hope this information proves useful! Best of luck implementing these workarounds.

Access Playground Link

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 is the method to make a String bold when sending it through a messaging service?

Here is the structure of my service: import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root', }) export class MessageService { messages: string[] = []; add(message: string) { this.messages.push(message); ...

How to nullify the valueChanges pipe in Angular RxJS until the observable is resolved

A challenge I am facing is piping the valueChanges from a select element to trigger the appropriate API request and displaying a spinner until the response is received. Additionally, I am trying to utilize publish() and refCount() methods so that I can use ...

Creating a dynamic list in Typescript from a tuple array without any intersections

const colors = ["red", "blue", "green", "yellow"] as const; const buttonSizes = ["small", "medium", "large"] as const; type ColorType = (typeof colors)[number]; type SizeType = (typeof b ...

Retrieve the formcontrolname property of a FormGroup within a FormArray

I am currently facing an issue with my code. In my FormGroup, I have a FormArray containing 3 FormControls. My goal is to iterate through the FormArray and display the values of each control in a form. However, I am unsure about what to use for the formCon ...

Angular error: Attempting to access the value property of an undefined object

When attempting to delete a row from a table, an error occurred stating "TypeError: Cannot read property 'value' of undefined" after placing the delete button at the end of a row. I watched this video tutorial for guidance on deleting a row witho ...

Modifying an @Input element within a component does not result in any changes being reflected in the parent component

Here is the scenario with my component: @Component({ selector: 'child' }) export class ChildComponent { @Input() childObject: ChildObject; changeObject(newObject: ChildObject){ childObject = newObject; } } After calling ...

Error message: 'React TypeScript: (props: OwnProps) => Element' cannot be assigned to type 'FunctionComponent<{}>'

Everything seems to be working fine as a JSX element... interface OwnProps { children: JSX.Element; location: Location; } export function Layout(props: OwnProps): JSX.Element { However, when I convert it into a functional component, an error occurs ...

Discovering the generic type from an optional parameter within a constructor

Looking to implement an optional parameter within a constructor, where the type is automatically determined based on the property's type. However, when no argument is provided, TypeScript defaults to the type "unknown" rather than inferring it as "und ...

React input delay handling during onChange event

Upon closer inspection, I've come across an issue that I had not previously noticed. I am unsure if there is a bug within my code or if the onChange function always behaves in this manner, but I am experiencing input delay and am uncertain on how to r ...

Developing a Liferay 7.0 portlet using Angular and TypeScript

Is it possible to develop a Liferay 7.0 portlet using Angular (2, Angular Material v1.1.1) and TypeScript? I am aware that it is possible to incorporate Angular 1.4 library and create a portlet using plain JavaScript/Angular code, as mentioned in this sou ...

Issue with the MUI Autocomplete display in my form

Recently, I started using Material UI and Tailwind CSS for my project. However, I encountered an issue with the Autocomplete component where the input overlaps with the following DatePicker when there is multiple data in the Autocomplete. I have attached a ...

What could be the reason behind the absence of TypeScript source code in the dist folder?

While using Angular CLI compiler, it generates source-maps (.map files) that allow debugging of the original TypeScript code in Chrome developer tools. This feature works seamlessly when developing locally with the JIT compiler/ng serve. However, upon bui ...

What prevents me from employing my nestjs unique decorator within a constructor?

I am looking to develop a personalized decorator that fetches tenant information. This is the current code snippet I have: export type TenantInfo = { token: string id: string } export const TenantInfo = createParamDecorator( (data: unknown, cont ...

What is the method for retrieving the second type of property from an array of objects?

I have a collection of objects that map other objects with unique identifiers (id) and names (name). My goal is to retrieve the name corresponding to a specific id. Here is my initial approach: const obj = { foo: { id: 1, name: 'one' }, ...

Is it possible to deduce the types of a particular value based on an interface that is provided as a generic parameter?

As a newcomer to TypeScript, I may be approaching this issue the wrong way. I am curious if there is a generic type that can handle an interface like {[key: string]: string | boolean} and allow for knowing the specific type of any value inside the consumin ...

Instead of simply displaying the pages of the PDF file, 'react-pdf' inserts HTML content after each page

A new component was created: import React from 'react'; import { Document, Page, pdfjs } from 'react-pdf'; import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/aj ...

Error in React PapaParse when parsing data

When I attempt to parse a CSV file named "dog.csv" using the papaparse parse function within my React component, an error occurs: No overload matches this call. The last overload resulted in the following error message. Argument of type 'string&apos ...

What is the reason behind the file not found error encountered when running Deno with the command "echo hello"?

Exploring Deno's standard library, I encountered an issue with Deno.run - a function designed to create a new subprocess. Here is the example provided in the documentation: const p = Deno.run({ cmd: ["echo", "hello"], }); When I attempt to run ...

Why is the `node-config` configuration undefined within a TypeScript Jest environment?

I have a TypeScript module that is functional in both development and production environments. It utilizes https://github.com/lorenwest/node-config. When I attempt to import it into Jest for testing purposes, I encounter an error suggesting that the config ...

Is there a way to retrieve the setState function from React Context and establish it as the initial value within createContext?

I am currently working with a very basic react context that looks like this: import { FC, createContext, useState, Dispatch, SetStateAction, PropsWithChildren } from "react" export const UserContext = createContext<UserContextType ...