What is the best way to implement a generic parameter with constraints in an abstract method?

Take a look at this scenario:

interface BaseArgs {
  service: string
}

abstract class BaseClass {
  constructor(name: string, args: BaseArgs) {
    this.setFields(args)
  }

  abstract setFields<T extends BaseArgs>(args: T): void
}

interface ChildArgs extends BaseArgs {
  newField: string
}

class Child extends BaseClass {
  setFields(args: ChildArgs): void {}
}

When I compile this code in Typescript, I encounter the following error:

Property 'setFields' in type 'Child' is not assignable to the same property in base type 'BaseClass'.
  Type '(args: ChildArgs) => void' is not assignable to type '<T extends BaseArgs>(args: T) => void'.
    Types of parameters 'args' and 'args' are incompatible.
      Type 'T' is not assignable to type 'ChildArgs'.
        Property 'newField' is missing in type 'BaseArgs' but required in type 'ChildArgs'.

Although I want to ensure that the argument (args) must extend BaseArgs, it seems like simply having Child's setFields method accept a type that extends BaseArgs is not satisfying the compiler. What am I overlooking here?

I experimented with using just T without the constraint, but the compiler recommended adding the extends ChildArgs constraint with

This type parameter might need an extends ChildArgs constraint.
.

I initially hoped that in any subclass extending the abstract base class, setFields could handle any type as long as it extended BaseArgs.

Answer №1

When it comes to generic functions and methods, the type arguments are determined by the caller rather than the implementer. The implementer can set constraints on the type parameter, but it is ultimately up to the caller to choose a compatible type argument within that constraint. Even if the compiler infers the type argument for the caller, it does so on behalf of the caller.

If you have instances of BaseClass, they follow an interface like:

interface BaseClass {
  setFields<T extends BaseArgs>(args: T): void
}

This implies that with an instance i of BaseClass, you can call i.setFields(arg) with any arg as long as it meets the requirements of being assignable to BaseArgs. Subclasses need to comply with the typings of their superclasses; for example, in class Child extends BaseClass {}, every Child instance must also be a BaseClass instance. Therefore, Child's setFields() method must allow the caller to provide any BaseArgs-compatible argument they want.

You cannot restrict the caller's choice of args like this:

class Child extends BaseClass {
  setFields(args: ChildArgs): void { } // error!
}

The implementer is limited while the caller has freedom in selecting the type arguments for generic functions and methods.


If your intention is not to have setFields be generic at all, aiming for the implementer to decide the argument type, then what you really need is the BaseClass class to be generic. Generic types (such as generic classes, interfaces, or type aliases) work differently in terms of their generic parameters scope:

abstract class BaseClass<T extends BaseArgs> {
  constructor(name: string, args: T) {
    this.setFields(args)
  }
  abstract setFields(args: T): void
}

This is akin to:

interface BaseClass<T extends BaseArgs> {
  setFields(args: T): void
}

Now, there isn't such thing as "an instance of BaseClass" without specifying the type argument. It can only exist as "an instance of BaseClass<BaseArgs>" or "an instance of BaseClass<SomeOtherArgs>". Thus, a subclass of BaseClass can extend beyond just BaseClass to

BaseClass<T></code for any desired <code>T
. For instance, the following is acceptable:

class Child extends BaseClass<ChildArgs> {
  setFields(args: ChildArgs): void { } // okay
}

Since an instance i of Child is essentially an instance of BaseClass<ChildArgs>, only a ChildArgs argument can be used with i.setFields(). This setup grants the implementer control over the type arguments while restricting the caller.

Link to Playground code

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 best approach to retrieve all user information using React with TypeScript and the Redux Toolkit?

I'm currently using React with TypeScript and Redux Toolkit, but I've hit a roadblock trying to retrieve user information. Below is my userSlice.ts file: export const userSlice = createSlice({ name: "user", initialState: { user: null, } ...

Exploring table iteration in Angular 7

I am looking to create a table with one property per cell, but I want each row to contain 4 cells before moving on to the next row... This is what I want: <table> <tr> <td> <mat-checkbox>1</mat-checkbox& ...

Having difficulties incorporating a selected custom repository into a module

Issue with Dependency Injection in NestJS Currently, I am working on implementing SOLID principles in my NestJS project by decoupling my service layer from TypeOrm. One of the benefits of this approach is the ability to switch between using an InMemoryRep ...

Unsure about the commit hash referenced in the tsd.json file

Explore the history of angular-ui-router d.ts file using the commit hash Within my tsd.json file, I have the following details: { "version": "v4", "repo": "borisyankov/DefinitelyTyped", "ref": "master", "path": "typings", "bundle": "typings/tsd ...

The `Home` object does not have the property `age` in React/TypeScript

Hey there, I'm new to React and TypeScript. Currently, I'm working on creating a React component using the SPFX framework. Interestingly, I'm encountering an error with this.age, but when I use props.age everything seems to work fine. A Typ ...

Transformation occurs once you subscribe to an observable entity

Within the x.component.ts, I initiate the getSomething() method from y.service.ts. Since this method returns an observable, I subscribe to it. However, I encounter a peculiar issue where an object with 4 elements, one being an array of number arrays (numbe ...

Combining Two Dropdown Selections to Create a Unique Name using Angular

I am facing a challenge with 2 dropdown values and input fields, where I want to combine the selected values from the dropdowns into the input field. Below is the HTML code snippet: <div class="form-group"> <label>{{l("RoomType")}}</labe ...

Implementing Asynchronous context tracking within a Remix application utilizing Express as the server

Utilizing Remix with Express as the server, I aim to develop an Express middleware that establishes an async context to grant all downstream functions (especially those in the "backend" Remix code) access to this context within the scope of a single reques ...

Ways to indicate in Typescript that a value, if it exists, is not undefined

Is there a way to represent the following logic in TypeScript? type LanguageName = "javascript" | "typescript" | "java" | "csharp" type LanguageToWasmMap = { [key in LanguageName]: Exclude<LanguageName, key> ...

Expo background fetch initialized but not activated

During the development of my React Native app, I encountered the need to perform periodic background fetches from another server. To achieve this, I utilized two classes from Expo: import * as BackgroundFetch from 'expo-background-fetch'; import ...

Can a TypeScript generic version of the Y-combinator be successfully executed?

Here is an interesting JavaScript implementation of the Y-combinator: const Y = g => (x => g(x(x)))(x => g(x(x))) //or const Y = f => { const g = x => f(x(x)) return g(g) } I've been thinking, could it be possible to create a TypeS ...

Determine the generic parameter of the output type by analyzing the resolved value of a data type within the function

I am looking to automatically determine the generic parameter of the return type by utilizing the resolved value of a type within the function. Consider the following: export type Context = any; export type Handler<T> = (ctx: Context) => Promise& ...

The error code TS2345 indicates that the argument '{ read: typeof ElementRef; }' cannot be assigned to the parameter '{ read?: any; static: boolean; }'

Currently in the process of updating my Angular application from version 7 to version 8. Upon running ng serve, I encounter the following error: Error TS2739: Type '{}' is missing the following properties from type 'EmployeeModel': stat ...

Elements are unresponsive to scrolling inputs

My Ionic 2 input elements are not scrolling to the top when the keyboard is shown. I've tried everything I could find on Google, making sure the keyboard disable scroll is set to false. However, I still can't figure out what's causing the sc ...

Tips for accurately implementing the onHoverIn TS type in the React Native Web Pressable component

I'm working with React Native Web and Typescript, and I want to integrate the React Native Web Pressable component into my project. However, I encountered an issue where VSCode is showing errors for React Native Web prop types like onHoverIn. The pro ...

Jest encountering errors when compiling a generic function

I'm able to successfully run my Node app, but running tests on a class with Generics is causing an error. The test code looks like this: import { Request, Response } from 'express'; import { JsonWebTokenError } from 'jsonwebtoken' ...

Unexpected runtime error when using Prisma with NodeJS and Express

While starting the server, I encounter an error message saying [ERROR] 14:48:46 Error: Cannot find module './runtime/library'. The stack trace points to the directory named prisma. Even after executing npx prisma generate, the directory called p ...

Creating a comprehensive object within a single interface using Typescript

Here is an example of an Object in TypeScript: export class test{ recordname: string; comments: [{ comment: string }] } To define it using one interface instead of multiple interfaces, you can try something like this: export int ...

Using a configuration file with the JavaScriptServices React-Redux template

I have come across this question many times on different platforms, but I haven't been able to make it work for me. The issue is that I am using an API within a React component (with TypeScript 2.4.1 and webpack 2.5.1): .... fetch("some/url/api/", me ...

Strategies for effectively searching and filtering nested arrays

I'm facing a challenge with filtering an array of objects based on a nested property and a search term. Here is a sample array: let items = [ { category: 15, label: "Components", value: "a614741f-7d4b-4b33-91b7-89a0ef96a0 ...