Expanding a generic class by introducing a new generic parameter

I have a basic framework class:

export class BaseClass<
  A extends boolean = false,
  B extends boolean = false,
> {
  readonly fieldA: A;
  readonly fieldB: B;

  constructor(options: { 
    readonly?: A, 
    many?: B 
  } = {}) {
    // @ts-ignore
    this.fieldA = options?.readonly || false;

    // @ts-ignore
    this.fieldB = options?.many || false;
  }

  public method1 = (data: any): any => { return }
  public method2 = (data: any): any => { return }
}

When I inherit from it in a class without generics, everything works smoothly:

export class SubClass<
  X extends boolean = false,
  Y extends boolean = false,
> extends BaseClass<X, Y> {
  method1 = (data: any) => new Date(data)
  method2 = (data: any) => new Date(data).toISOString()
}

const instance = new SubClass({ many: true })
typeof instance.many // true

However, when I extend into a class with additional generic parameters, the assignment of BaseClass generics does not function correctly and certain values default to their initial settings.

export class NewField<
  P extends any = any,
  Q extends boolean = false,
  R extends boolean = false,
> extends BaseClass<Q, R>{
  method1 = (data: any) => data as P
  method2 = (data: any) => data as P
}

type P = "a" | "b" | "c"
const newObject = new NewField<P>({ many: true, readonly: true });
// Some tests for reassurance
assert<Equals<typeof newObject["readonly"], true>>() // Type 'false' does not meet the requirement 'true'
assert<Equals<typeof newObject["many"], true>>() // Type 'false' does not meet the requirement 'true'

Can you provide suggestions on how I can ensure that the extended NewField class returns the value based on its generic parameter P, while also correctly setting the readonly and many fields?

Answer №1

According to comments made by Thomas, TypeScript does not currently support partial type arguments inference. There is an ongoing issue at microsoft/TypeScript#26242 addressing this.

The reason for this lack of implementation could be due to the introduction of default values for type parameters around the same time. This has led to potential confusion between inferring the rest of the types and replacing them with default values when using syntax like EnumField<T>.

If a type parameter has a default value, it creates ambiguity in how to proceed. Using a syntax like EnumField<T, infer, infer> may be necessary, but this is less user-friendly since users should not have to know the exact number of generics being used. There have been discussions about implementing a rest syntax like EnumField<T, ...infer>, or exploring variadic type argument support, but these proposals also face challenges and complexities.


In essence, the current state requires users to either provide all required type parameters or none at all. Any type parameter with a default value will not be inferred, but replaced by its default value instead.


Potential Solutions

One workaround involves using a factory function with the first generic curried:

const createEnumField = <T>() =>
  <R extends boolean, M extends boolean>(options: { readonly?: R, many?: M}) =>
    new EnumField<T, R, M>(options);

const serializer = createEnumField<T>()({ many: true, readonly: true });

Another approach is to introduce an additional fake runtime parameter to the constructor or options object:

const _ = null as any;

export class EnumField<
  T extends any = any,
  R extends boolean = false,
  M extends boolean = false,
> extends BaseSerializer<R, M> {
  fromDTO = (data: any) => data as T
  toDTO = (data: any) => data as T
  
  constructor(options: { readonly?: R, many?: M, returnType?: T} = {}) {
    super(options);
  }
}

type T = "a" | "b" | "c"

const serializer = new EnumField({ many: true, readonly: true, returnType: <T>_ })

I advise against structuring the code in a way that necessitates passing every type parameter, as it can be challenging for users when dealing with dynamically generated constructor arguments. It may lead to mistakes or require importing utility types to properly match the type arguments with the dynamic arguments produced.

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

The Typescript error "Attempting to call a function that does not have any callable signatures.(2349)"

Could you please assist me in resolving this issue: type IValidator = (value?: string) => string | undefined; type IComposeValidators = (validators: ((value?: string) => string | undefined)[]) => IValidator; export const composeValidators: ICompo ...

Can you provide guidance on how to extract the ID from a Firebase reference in the "onCreate" or "onUpdate" triggers?

I have a scenario where I need to fetch specific values under {id} in the .onUpdate() method. Var3 is a nested object, while var2 is a single variable. Is there a way to extract {id} from onUpdate and pass it as an argument to customMethod so that I can ut ...

Strategies for redirecting search queries when adding a new path

Issue I am facing a challenge with pushing a new path to the URI while maintaining existing search queries. For example: Current URL: https://example.com/foo?bar=123&foobar=123 When I use history.push('newPath'), I end up with https://exa ...

Is there a problem with the props being passed? Can someone verify this

Blockquote Having trouble passing props, Parent component: props: { data: { type: Object as PropType<FormatOrderItem>, default: () => {} } I'm facing an issue when trying to pass props from the parent component to the ch ...

Choose an option from the dropdown menu

I am facing a challenge with filtering data on a select element due to an overwhelming amount of options. Here is how I attempted to implement it: <mat-form-field> <mat-select placeholder="Unit.." (change)="onTabChange(element, $event.valu ...

What is the process for generating an alert box with protractor?

While conducting tests, I am attempting to trigger an alert pop-up box when transitioning my environment from testing to production while running scripts in Protractor. Can someone assist me with this? ...

Error: To execute NPX command locally from Google Maps API documentation, make sure to call npm.load(callback) as required

Attempting to execute the Google Maps API example locally using this command: npx @googlemaps/js-samples init directions-waypoints googlemapssample However, every time I try to run the npx command locally, it fails after a short period and I encounter the ...

Is Angular 4 failing to set headers properly or is Express.js searching in the wrong place?

When interacting with an Express.js API, I encountered a issue regarding the handling of auth tokens. The problem arose when sending the token in the request headers using Angular 4 compared to Postman. In Postman, setting the header named 'Authorizat ...

Modifying app aesthetics on-the-fly in Angular

I am currently working on implementing various color schemes to customize our app, and I want Angular to dynamically apply one based on user preferences. In our scenario, the UI will be accessed by multiple clients, each with their own preferred color sch ...

Require a parameter in the return function when the generic is not null in the caller within Typescript

In TypeScript version 5.0.2 I am working on a function that returns an array of 3 functions. I want the purchase function to be typed in such a way that it only requires a requirement parameter if the specified product has one (as indicated in the product ...

Angular JSON converter - Transform XML data to JSON format

Struggling to convert XML API response to JSON using xml2js library, facing issues with getting 'undefined' in the console. Here is my API service: export class WordgameService { public apiUrl = "http://www.wordgamedictionary.com/api/v1/reference ...

Is there a way to prevent IntelliJ from creating .js files when working with .ts source code?

Working on a mixed Java/Typescript project with Maven as the build tool, I utilize the frontend-maven-plugin to successfully build from the command line. However, I am encountering an issue with IntelliJ 2018.2 where it keeps transpiling .js files for my . ...

Using Typescript to Declare Function and React Component Types

Currently challenging myself to delve into Typescript (ugh). Usually, I can deduce the appropriate type by analyzing the return values. Nonetheless, in this particular scenario, that method is proving ineffective. type SomeReactAProps = { Type1: ReactEle ...

Tips on retrieving enum values in typescript

Having trouble retrieving values from an enum? Check out this snippet of code: export const enum ComplianceType { ENGINEER_ASSESMENT = 'ENGINEER_ASSESMENT', CONSTRUCTION_COMPLIANCE = 'CONSTRUCTION_COMPLIANCE', ARCHITECTURE_ASSIGN ...

The intricate process of selecting and organizing data into a structured

In my code, there is an array called listSelected that gets updated after each selection in another grid. const listSelected = [{ "_id": "10", "age": 35, "name": "Paige Zamora", "gender": "female", "company": " ...

Creating mock objects for Karma/jasmine testing in Angular

Could someone offer a detailed explanation on how to generate Stubs for mocking services in Angular Karma testing? I would greatly appreciate either a comprehensive example or a helpful link. Once the stub is created, what's the best way to write tes ...

Is there a specific type required for the Vue `install` function? Is it necessary to have the `directive` property on the `Vue`

I've been working on implementing a Vue directive in typescript, but I'm struggling to determine the correct types to use for the Vue install function. Using install(Vue: any): void feels a bit strange to me. I attempted importing Vue and using ...

Creating a JSON object from two arrays is a simple process

Consider the following two arrays: let values = ["52", "71", "3", "45", "20", "12", "634", "21"]; let names = ["apple", "orange", "strawberry", &q ...

What is the cause of the display name missing in the Material-UI Typescript ListItemLink example when using React.forwardRef?

Explore the Material-UI documentation guide on incorporating Typescript in an example demonstrating the creation of a ListItemLink component: Visit the official documentation function ListItemLink(props: ListItemLinkProps) { const { icon, primary, to ...

Utilizing material-ui with Autocomplete featuring various value and option types

In my code, I am looking to store only an option's ID in a value For autocomplete functionality, the value's type and the option's type need to be the same My solution was to change the value in onChange, which worked successfully However ...