How should a list of classes that inherit from a common base class be properly typed?

When working with TypeScript, I'm attempting to correctly type a variable that is an "Array of Classes inheriting from a base class".

The code I have results in the error 'Cannot create an instance of an abstract class. ts(2511)', which makes sense. However, my intention is not to instantiate a member of Base, but rather its descendants.

abstract class Base {
  abstract execute(param: string): void;
  protected name: string;

  constructor(name: string) {
    this.name = name;
  }

  public commonMethod() {
    console.log("Common Method");
  }
}

class A extends Base {
  execute() {
    console.log(`Hello from A, ${this.name}`);
    this.commonMethod();
  }
}

class B extends Base {
  execute() {
    console.log(`Hello from B, no-name`);
  }
}

const list: typeof Base[] = [A, B];

const names = ["Phil", "Andy", "Bob"];

names.map((name) => list.map((cl) => new cl(name)));

How can I accurately type const list: ???[] = [A, B];

Switching to const list: typeof A[] = [A,B] works, but it wrongly suggests that all entries in the list are of type A.

Playground link

Answer №1

To avoid using the class itself, you can utilize a constructor signature:


const list: Array<new (name: string) => Base> = [A, B];

const names = ["Phil", "Andy", "Bob"];

names.map((name) => list.map((cl) => new cl(name)));

Playground Link

If you require access to statics as well, consider this alternate approach:

const list: Array<Omit<typeof Base, never> & (new (name: string) => Base)> = [A, B];

const names = ["Phil", "Andy", "Bob"];

names.map((name) => list.map((cl) => new cl(name)));
list[0].foo();

Playground Link

Answer №2

The issue here is that the error message is accurate. The type of list is identified as typeof Base[], where Base cannot be instantiated. Unfortunately, there doesn't seem to be a way to specify that list should be "an array of non-abstract constructors derived from Base." :-)

Given this limitation, one approach could be to allow TypeScript to treat Base as non-abstract and turn direct instantiation into a runtime error instead of a compile-time error by preventing direct construction in the Base constructor:

class Base {
    // ...

    constructor(name: string) {
        if (new.target === Base) {
            throw new Error("Base cannot be directly instantiated");
        }
        this.name = name;
    }

    // ...
}

For a complete example, you can refer to the code in this playground link:

// Your code snippet goes here

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

How can I apply styling to Angular 2 component selector tags?

As I explore various Angular 2 frameworks, particularly Angular Material 2 and Ionic 2, I've noticed a difference in their component stylings. Some components have CSS directly applied to the tags, while others use classes for styling. For instance, w ...

BehaviorSubject Observable continuously notifies unsubscribed Subscription

Utilizing a service called "settings", initial persisted values are read and provided through an observable named "settings$" to components that subscribe to it. Many components rely on this observable to retrieve the initial values and exchange updated va ...

Resolving the global provider in Angular2 Typescript with the help of an Interface

In the realm of my Angular2 application, I have two essential services called WebStorageService and MobileStorageService, both of which impeccably implement an interface known as IStorageService. Interestingly, in my magnificent main.component, I elegantly ...

Accessing the value of a FormControl in HTML代码

Modifying the value of a form select element programmatically presents an issue. Even after changing the value in the form, the paragraph element "p" remains hidden. However, if you manually adjust the form's value, the visibility of the "p" element ...

Error: Trying to access a property that is not declared on an empty object

Using a fully patched Visual Studio 2013, I am integrating JQuery, JQueryUI, JSRender, and TypeScript into my project. However, I am encountering an error in the ts file: Property 'fadeDiv' does not exist on type '{}'. While I believ ...

Encountered an error of 'No overload matches this call' while configuring ContextApi alongside useReducer with Typescript

I am currently setting up ContextApi and useReducer with typescript. I am encountering an error with my "issuesInitialState" variable in useReducer, where I receive the following message: No overload matches this call. Overload 1 of 5, '(reducer: ({ ...

Next.js does not support tooltips with custom children components

I created a unique Tooltip component and I'm attempting to include NextLink as the children. However, I encountered an error similar to the one below. Warning: Failed prop type: Invalid prop `children` supplied to `ForwardRef(Tooltip)`. Expected an e ...

Executing a Typescript function in an MVC5 cshtml file, optimized with webpack bundling

Brand new to webpack, and I'm facing obstacles in merging MVC components with webpack AND typescript. Take a look at my code combinations below: webpack.config.js: var wbConfigEntries = { "jqkendoMain": [ paths.appjs + "main.ts" ] }; ...

Clicking the button in Angular should trigger a series of functions to be

It seems like I'm struggling with a simple question due to my lack of experience in this area. Any guidance or help would be greatly appreciated. I have a button that should trigger multiple functions when clicked, all defined in the same ts file. Wh ...

React Date-Picker is unable to process a date input

Recently, I've been working on integrating a date picker into my application. I came across this helpful library that provides a date picker component: https://www.npmjs.com/package/react-date-picker So far, I have set up the component in the follow ...

Is it advisable to flag non-(null|undefined)able type arguments as a type error?

Can the function throwIfMissing be modified to only flag test1 as a compiler error? function throwIfMissing<T>(x: T): T { if (x === null || x === undefined) { throw new Error('Throwing because a variable was null or undefined') ...

Locate any identical elements within an array and substitute them with their corresponding frequency

Let's say I have an array that looks like this: arr = [25, 25, 25, 20, 15, 10, 10, 5]; My goal is to count the number of duplicate values (for example, three 25s and two 10s) and create a new array that would look like this: newArr = ['25 * 3& ...

Tips for managing asynchronous HTTP requests in Angular 4:

I have a collection of "cards" from which I need to retrieve information using an Angular service. The challenge is that this information is obtained through http requests to an API, and I require the returned data to be processed only after all requests a ...

Ensuring Map Safety in Typescript

Imagine having a Map structure like the one found in file CategoryMap.ts export default new Map<number, SubCategory[]>([ [11, [100, 101]], [12, [102, 103]], ... ]) Is there a way to create a type guard for this Map? import categoryMap fro ...

How can I create a Typescript declaration where one value is dependent on the type of another?

Here is some code that I am working on: type ValidationResult = 'OK' | 'MISSING_FIELDS' | 'DUPLICATED_FIELD'; type ValidationResultOkDetails = null; type ValidationResultMissingFieldsDetails = [string]; type ValidationResultD ...

Incorporating SVG graphics within a SharePoint web part

I am in the process of developing a webpart for SharePoint using the SharePoint Framework, TypeScript, and ReactJS. I have encountered an issue while trying to incorporate an svg image into my webpart code, resulting in build errors. Initially, I used the ...

Refreshing the Angular 5 array does not happen automatically after pushing elements

After designing a Mat Dialog box with fields to enter information, clicking the create button on the dialog should add a new ticket to an array in the ticket service. However, despite the dialog box automatically closing after creating the ticket, the newl ...

Angular - Dynamically change the height of an input element based on its content's length

I'm looking to automatically adjust the height of my input text based on its content. I have a solution that works when the user is actively typing, triggering the (input) event and calling my adjustHeight function to update the input element's h ...

Unable to utilize combined data types in React properties

In my theme.interface.ts file, I defined the following 2 types: type ThemeSize = | 'px' | '1' | '1/2' | 'full' | 'fit' type ThemeWidthSpecific = 'svw' | 'lvw' | 'dvw&apos ...

The Vuex MutationAction decorator cannot be assigned to a TypedPropertyDescriptor

I am a beginner in Typescript and apologize in advance if this is a rookie mistake. I am struggling to resolve this TS error: @Module({ namespaced: true, name: "Admin" }) class Admin extends VuexModule { public adminUserList: UserList = []; ...