Creating a generic anonymous class in TypeScript can be achieved by specifying the class body

The title might be a bit misleading, but I'm struggling to articulate my issue.

I aim to pass a generic class (or just the class body) as an argument to a function. The provided class should then infer its generics automatically.

class Builder<EXT, INT = EXT> {
    // some builder stuff that alters the generic T
    withBar(): Builder<EXT, INT & { bar: string }> {
      return this as any;
    }

    // here lies the challenge:
    build(clazz: MyClass<INT>): MyClass<EXT> {
       return wrap(clazz); // this method already exists and functions correctly
    }
}

Usage:

const builder = new Builder<{ foo: number }>();
// EXT = { foo: number }, INT = { foo: number }

builder = builder.withBar();
// EXT = { foo: number }, INT = { foo: number, bar: string }

builder.build(class { /* here I should be able to access this.foo and this.bar */ });
// Here I just want to provide the class body,
// because I don't want to type all the generics again.
// I don't want to specify `MyClass<?>`,
// because the correct type is already specified within the Builder

As an (ugly) "workaround," I discovered a method to provide all class methods individually and then construct a class from them. Something like this:

class Builder<EXT, INT = EXT> {
    build(args: {method1?: any, method2?: any}): MyClass<EXT> {
       class Tmp {
         method1() {
           return args.method1 && args.method1(this);
         }
         method2(i: number) {
           return args.method2 && args.method2(this);
         }
       }

       return wrap(Tmp);
    }
}

But it's quite messy.

Essentially, I simply want to provide the class body to the build method. Then this method would create a class from it, invoke wrap, and return the result.

Is there a way to achieve this?

EDIT: Another attempt to clarify my issue:

Currently, I need to use the code in this manner:

builder.build(class extends AbstractClass<{ foo: number, bar: string }> {
    private prop: string;
    init() {
      this.prop = this.data.foo   // provided by AbstractClass 
          ? 'foo'
          : 'bar'
    }
    getResult() {
      return {
        foo: this.prop,
        bar: this.data.bar  // provided by AbstractClass
      }
    }
})

As depicted, I have to specify the generics of AbstractClass. I prefer not to specify the type since builder already knows it.

I only wish to provide the body of the class without reiterating the generic type. Something akin to this:

builder.build(class extends AbstractClass<infer the type magically!> {
    ...
    getResult() {
        return { this.data.foo }
    }
})

Or even this:

builder.build(class {
    ...
    getResult() {
        return { this.data.foo }
    }
})

Answer №1

It's not completely clear to me what you're looking for or if it's even possible. However, I have some suggestions...

From what I gather, MyClass<T> appears to be a constructor that returns a type T. This can be represented by a constructor signature like this:

type Constructor<T> = new (...args: any[]) => T;

So maybe the Builder should look something like this:

class Builder<EXT, INT = EXT> {
  // some builder stuff, which changes the generic T
  withBar(): Builder<EXT, INT & { bar: string }> {
    return this as any;
  }

  // here's possibly a solution:
  build(clazz: Constructor<INT>): Constructor<EXT> {
    return wrap(clazz) as any; // unsure about 'as any'
  }
}

When calling it... you cannot alter types of values in TypeScript, so reassigning builder is not possible. However, you can still chain methods (or give intermediate values new names). Here's an example using chaining:

const builder = new Builder<{ foo: number }>();

const ctor = builder.withBar().build(class {
  foo = 10;
  bar = "you";
});

This approach works, but remember to define foo and bar within the anonymous class body. TypeScript will throw errors if they are omitted. So while you do "have access" to them, it may not be as convenient as desired.


If this isn't satisfactory, please provide more information and I'll try to assist further. Good luck!

Answer №2

Just had a brilliant idea that introduces some extra, unused code but successfully satisfies type inference.

The concept is rather straightforward: Just leverage the power of typeof!

class Generator<EXTENSION, INTERNAL = EXTENSION> {
    // contains generator logic that modifies generic T
    withAdditionalFeature(): Generator<EXTENSION, INTERNAL & { feature: string }> {
      return this as any;
    }

    construct(constructor: (type: INTERNAL) => Creator<INTERNAL>): MyObject<EXTENSION> {
      return wrap(constructor(null as any) as any) as any;
    }
}

interface Creator<L> {
    new (info: L): any;
}

Now, you can utilize the type: INTERNAL in the following manner:

// behold the "magic"
generator.construct((type) => class extends BaseClass<typeof type> {
    getResult() {
        return { this.data.foo }
    }
})

I'm uncertain if there exists a superior solution.

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 potential issue could result in a property length of null error while working with a Material Data Table?

I'm experiencing a similar issue as the one discussed in this post, but none of the suggestions there have resolved my problem, and my scenario has some differences. In my case, a parent component is assigning an array to a child component's inp ...

The argument passed cannot be assigned to the parameter required

Currently, I am in the process of transitioning an existing React project from JavaScript to TypeScript. One function in particular that I am working on is shown below: const isSad = async (value: string) => { return await fetch(process.env.REACT_AP ...

Using ESLint to enforce snake_case naming conventions within TypeScript Type properties

When working with TypeScript, I prefer to use snake_case for properties within my Interfaces or Types. To enforce this rule, I have configured the ESLint rule camelcase as follows: 'camelcase': ["error", {properties: "never"}], Even though the E ...

Enhance the TypeScript interface by dynamically appending new fields with specific naming conventions

My interface is structured like this: interface ProjectCostData { purchasePrice: number; propertyValue: number; recentlyDamaged: boolean; } Now I am looking to dynamically create a new interface based on the one above: interface ProjectCostDataWithS ...

What steps can I take to fix error TS7015 in my TypeScript, Next.js, and React application when I encounter an index expression that is not a number data type?

I encountered an error stating "Element implicitly has an 'any' type because the index expression is not of type 'number'.ts(7015)" The file I imported is import { useAmazon } from "@context/amazon"; I used it as follows con ...

execute the function once the filereader has completed reading the files

submitTCtoDB(updateTagForm:any){ for(let i=0;i<this.selectedFileList.length;i++){ let file=this.selectedFileList[i]; this.readFile(file, function(selectedFileList) { this.submitTC(updateTagForm,selectedFileList); }); } } } ...

Issue with interface result: does not match type

Hey there! I've been working on creating an interface in TypeScript to achieve the desired return as shown below: { "field": "departament_name", "errors": [ "constraint": "O nome do departam ...

Experiencing a 404 ERROR while attempting to submit an API POST request for a Hubspot form within a Next.js application

Currently, I am in the process of developing a Hubspot email submission form using nextjs and typescript. However, I am encountering a couple of errors that I need help with. The first error pertains to my 'response' constant, which is declared b ...

How can ngx-modals detect when the modals have been closed?

I have integrated ngx-modals into my project and I am looking to add a boolean value to it. When the modals open, I want to set this boolean to "true", and when they close, I need it to be set to "false". Regardless of whether the modal is closed using the ...

Specifying the data structure of a complex nested Map in TypeScript

Struggling to create a deeply nested (recursive) Map in Typescript generically. My attempt is to convert the provided Javascript example to Typescript: const map1 = new Map([ ['key1', 'value1'] ]) const map2 = new Map([ ['keyA& ...

What causes TypeScript's ReadonlyArrays to become mutable once they are transpiled to JavaScript?

Currently, I am in the process of learning Typescript by referring to the resources provided in the official documentation. Specifically, while going through the Interfaces section, I came across the following statement: TypeScript includes a special t ...

Is there a way to receive a comprehensive report in case the deletion process encounters an error?

Currently, I am performing a delete operation with a filter based on 2 fields: const query = await Flow.deleteOne({ _id: flowId, permissions: currentUser!.id, }); After executing the delete operation, I inspect the query object to determine its su ...

Observable subscription does not result in updating the value

One of the challenges I'm currently facing in my Angular application is the synchronization of data from a server. To keep track of when the last synchronization took place, I have implemented a function to retrieve this information: fetchLastSyncDate ...

Error in TypeScript: Module 'stytch' and its corresponding type declarations could not be located. (Error code: ts(2307))

I'm currently developing a Next.js application and encountering an issue while attempting to import the 'stytch' module in TypeScript. The problem arises when TypeScript is unable to locate the module or its type declarations, resulting in t ...

Trouble accessing data property within a method in Vue 3 with Typescript

I am facing an issue with accessing my data in a method I have written. I am getting a Typescript error TS2339 which states that the property does not exist in the type. TS2339: Property 'players' does not exist on type '{ onAddPlayers(): vo ...

What is the best way to utilize nav and main-nav in CSS?

Working with HTML and CSS <nav class="nav main-nav"> </nav> Styling with CSS main-nav li{ Padding:0 5% } I am a beginner in HTML and CSS and facing an issue. I noticed that when using main-nav in CSS, I'm not getting the de ...

Angular 5 does not allow function calls within decorators

I encountered an issue while building a Progressive Web App (PWA) from my Angular application. When running ng build --prod, I received the following error: ERROR in app\app.module.ts(108,64): Error during template compile of 'AppModule' Fu ...

Can TypeScript provide a method for verifying infinite levels of nested arrays within a type?

Check out this example The concept behind this is having a type that can either be a single object or an array of objects. type SingleOrArray<T> = T | T[]; The structure in question looks like this: const area: ItemArea = [ { name: 'test1& ...

What is the correct way to assign a property to a function's scope?

Lately, I've been delving into Typescript Decorators to address a specific issue in my application. Using a JS bridge to communicate TS code between Android and iOS, we currently define functions as follows: index.js import foo from './bar' ...

Custom attributes given to Stencil web components in Vite/Vue3 will not trigger any reactions

Short backstory I initially set up my project with a vue-cli environment using Vue 2 and options-api. Recently, I decided to transition to create-vue, which is based on Vite with Vue 3 and Typescript. To incorporate web components from Stencil into my pro ...