Extracting the Type from an Unnamed Generic Class in TypeScript

I am struggling to find the most elegant solution for determining the type of anonymous generic classes that are created and returned from functions.


Below is a code snippet showing the function that generates anonymous classes and one of the functions within it.

Generics are necessary for TypeScript to provide return type and parameter property hints when using the get function.

export function createController<T, U extends { id?: number }>(cls: new () => T) {
    return class extends BaseController {
        public constructor() {
          super(cls.name.split(/(?=[A-Z])/).join('_').toLowerCase());
        }

        public async get(params: U): Promise<T | null> {
          try {
            const { data } = params.id ? await this.http.get(`${this.base}-${params.id}`) : await this.http.get('', { params });
            return deserialize(cls, search(data, 'data'));
          } catch (err) {
            return null;
          }
        }
    };
}

I encounter an issue when attempting to create and store the result of this procedure.


Take a look at how to create a UserController below.

export interface UserQueryParams {
    id?: number;
    username?: string;
}

const UserController = createController<User, UserQueryParams>(User);

Now, UserController instances can be easily created like regular classes by calling new UserController().

No problem arises when storing them directly like so:

// Correctly inferred as createController<User, UserQueryParams>.(Anonymous class)
const controller = new UserController();

However, if I want to create a class containing these controllers:

class Client {
    public controller: ???

    public constructor() {
        this.controller = new UserController();
    }
}

I struggle to determine the best way to type the property.

An error occurs when using

public controller: UserController
because UserController is a value and not a type.

Using

public controller: typeof UserController
also fails, as the constructor assignment becomes incompatible.

Almost there with

public controller: typeof UserController.prototype
, but the type information gets lost. It's identified as
createController<any, any>.(Anonymous class)
due to the "any"s used for generics.

A workaround would be:

const temp = new UserController();

// In the class..

public controller: typeof temp;

This method works, but having to create temporary instances of every controller generated is quite messy.

Is there another way to properly type the controller property?

Answer №1

I discovered a solution that I find quite elegant, utilizing the InstanceType feature introduced in TypeScript 2.8

import { UserController } from '@/controllers';

class Client {
    public controller: InstanceType<typeof UserController>;

    public constructor() {
        this.controller = new UserController();
    }
}

What's interesting is that I can also export the type within the same file under the same name as the generated UserController class, and TypeScript is smart enough to recognize that both the Type and Class are being exported.

// Inside './controllers/index.ts
export const UserController = createController<User, UserQueryParams>(User);

export type UserController = InstanceType<typeof UserController>;

// ... In some other file

// TypeScript knows you are importing both the Type and the Class!
import { UserController } from './controllers';

class Client {
    public controller: UserController; // Functions as type!

    public constructor() {
        this.controller = new UserController() // Acts as class instance!
    }
}

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

TS Mapped Type: Dynamically exclude specific keys based on their values

Seeking a method to create a mapped type that excludes specific keys based on the value of the mapped key. For instance: Consider an option: Options struct, where Options is a union type defined as: { type: DataType } or { type: DataType, params: DataPar ...

Tips on exporting a basic TypeScript class in an Angular 4 module

I am facing a challenge with packaging a TypeScript class as part of an Angular module for exporting it as a library using ng-packagr. For instance, here is my class definition - export class Params { language: string ; country: string ; var ...

Issue arising from Angular Compiler regarding Component Input when utilizing inherited or extended interfaces

I'm facing an issue with an Input() Variable in an Angular Component. The problem lies in a type-related error that occurs in the template file of the page. To reproduce the error, I have two interfaces named Bike and BikeMin, where Bike extends BikeM ...

How can I extend a third-party JavaScript library in TypeScript using a declaration file?

Currently, I am working on creating a .d.ts file for an external library called nodejs-driver. While I have been able to map functions and objects successfully, I am struggling with incorporating the "inherit from objects defined in the JS library" conce ...

Is there a RxJS equivalent of tap that disregards notification type?

Typically, a tap pipe is used for side effects like logging. In this scenario, the goal is simply to set the isLoading property to false. However, it's important that this action occurs regardless of whether the notification type is next or error. Thi ...

What is the proper way to specify the interface as Dispatch<Action>?

My goal is to create an interface with the dispatch function without using Redux. interface DispatchProps { dispatch: (action: { type: string }) => void; } export function addTwoToNumber({ dispatch }: DispatchProps) { dispatch({ type: '@addTwo ...

What is the significance of the initial "this" parameter in Typescript?

I came across this method signature: export function retry<T>(this: Observable<T>, count: number = -1): Observable<T> { return higherOrder(count)(this) as Observable<T>; } The first parameter is this and it is typed as Observabl ...

Retrieve class attributes within callback function

I have integrated the plugin from https://github.com/blinkmobile/cordova-plugin-sketch into my Ionic 3 project. One remaining crucial task is to extract the result from the callback functions so that I can continue working with it. Below is a snippet of ...

Encountering a Mongoose error during the development of a NestJs project

While working on my Nest project, I encountered an issue with the Mongoose package. When attempting to build the project using npm run build, an error appeared in the console: node_modules/mongoose/node_modules/mongodb/mongodb.d.ts:34:15 - error TS2305: Mo ...

A volume slide will decrease to zero if the 'mute' button is selected

Well, the title pretty much sums it up. I successfully managed to make it so that when you slide the volume to 0, it mutes the video. However, I am struggling with achieving this: if the video is muted, the volume slider should automatically move to 0. I ...

Creating a test scenario for displaying a list of posts

I am currently working on writing a test for the code snippet below, which essentially displays all blog posts with the most recent post appearing at the top. I am fairly new to React Testing Library and each time I try to include my components in the test ...

Enhabling single-click selection for cell edit in Primeng table, rather than having to double-click

My current setup involves using a p-table where cells are filled with integer data and can be edited. When I click once on a cell, the input text becomes visible with the cursor positioned at the end of the existing text. The objective is to have the entir ...

Creating a standard arrow function in React using TypeScript: A Step-by-Step Guide

I am currently working on developing a versatile wrapper component for Apollo GraphQL results. The main objective of this wrapper is to wait for the successful completion of the query and then render a component that has been passed as a prop. The componen ...

The compilation of TypeScript and ES Modules is not supported in Firebase Functions

Recently, I integrated Firebase Functions into my project with the default settings, except for changing the value "main": "src/index.ts" in the package.json file because the default path was incorrect. Here is the code that was working: // index.ts cons ...

What is the most efficient way to trigger an API call using Angular HttpClient in response to multiple events without repetitively subscribing to the API call method throughout my code?

In my Angular Cli app, I have a data table with dynamic filters and a search bar. Whenever the search value changes, pagination changes, or a filter is applied or removed, I need to update the table items and filter information by making an API call. My s ...

The TypeScript compiler is unable to locate the injected property within a Vue Object Component

Struggling with a work example involving injecting a service during Vue's bootstrapping process. Everything "works" fine in my JavaScript and Class-Component TypeScript versions. However, when using the Vue object API with TypeScript, the compiler th ...

React Navigation with TypeScript: The specified type is incompatible with the 'BottomTabNavigatorConfig' parameter

I'm currently in the process of developing a mobile app using React Native and I've chosen to manage my navigation with React Navigation V2. Recently, I came across some code on the official documentation that seemed perfect for what I needed: ...

Show the Prisma model by utilizing the model's designated name

I am working on creating a function that displays a specific Prisma model based on the model name provided as a parameter. The challenge is to ensure that TypeScript can verify if the specified model actually exists each time. /* file schema.prisma mod ...

Leverage TypeScript to enforce the value of a property based on the keys of another property

The issue at hand is illustrated in the following example: type ExampleType = { properties: { [x: string]: number; }; defaultProperty: string; }; const invalidExample: ExampleType = { properties: { foo: 123, }, defaultProperty: "n ...

"Revolutionizing the way we navigate: Angular's innovative

Presently, my focus is on incorporating route transitions into my project. I've employed a component that appears on click and triggers the corresponding service function: routeTransition(destination) { if (this.router.url !== destination) { t ...