Setting up a variable with a changing value

In a very specific scenario, the body of type varies based on the length_type attribute (as illustrated in the example).

enum LengthTypeEnum {
  SELECT = 'SELECT',
  STATIC = 'STATIC',
  CONDITION = 'CONDITION',
  PERIOD = 'PERIOD'
}

type LengthType<T extends LengthTypeEnum> =
  T extends LengthTypeEnum.SELECT ? { length_type: T, lengths: number[] } :
  T extends LengthTypeEnum.PERIOD ?  { length_type: T, length_min: number; length_max: number }:
  T extends LengthTypeEnum.STATIC ? { length_type: T, length: number }:
  T extends LengthTypeEnum.CONDITION ? { length_type: T, conditions: any[] }:
  {}
;

let variableLength: LengthType<LengthTypeEnum> | null = null

function handleSelect(length_type: LengthTypeEnum):void{
  variableLength = initializeLengthData(length_type)
}

function initializeLengthData<T extends LengthTypeEnum>(length_type: T):LengthType<T>{
  if(length_type === LengthTypeEnum.SELECT){
    return {length_type: length_type, lengths: []} as any as LengthType<T>
  }else if(length_type === LengthTypeEnum.PERIOD){
    return {length_type: length_type, length_min:0, length_max: 0} as any as LengthType<T>
  }else if(length_type === LengthTypeEnum.STATIC){
    return {length_type: length_type,length: 0} as any as LengthType<T>
  }else if(length_type === LengthTypeEnum.CONDITION){
    return {length_type: length_type,conditions: []} as any as LengthType<T>
  }
  return {} as LengthType<T> ;
}

The "Length Type" type functions correctly. Once I check the length_type value, the compiler can infer the structure of the rest of the variable.

However, there is an issue with initializing the variable when choosing the length_type. The code works but it relies heavily on using "as any" type casting which does not look elegant. Is there a way to avoid this or should I consider a different approach?

Minimal reproducible version in GUI example playground

Minimal reproducible version in Typescript only playground

Answer №1

TypeScript 5.3 has limitations when it comes to control flow analysis with generic type parameters. When checking conditions like

length_type === LengthTypeEnum.SELECT
within the function initializeLengthData(), the compiler can narrow the type of length_type from T to T & LengthTypeEnum.SELECT. However, it does not do the same for T itself. This leads to issues when trying to return
{length_type: length_type, lengths: []}
as a valid LengthType<T>, since evaluating LengthType<T> requires knowledge of what T is.

There are ongoing feature requests addressing this limitation, such as microsoft/TypeScript#33014 and microsoft/TypeScript#33912. Progress on this issue is anticipated in the near future as mentioned in the TS5.5 iteration plan at microsoft/TypeScript#57475, but until then, workarounds are necessary.


For generic scenarios involving multiple cases, TypeScript's current support involves representing it through indexed access with a generic key. By returning a value of type SomeInterface[K] where K is a generic type, alongside providing values of type SomeInterface and keys of type K, you can achieve the desired functionality using s[k].

To simplify this process, consider employing getters to delay property evaluation until they are accessed by indexing. Instead of lengthy conditional checks like

if (k === "a") { return f(); } else if (k === "b") { return g(); } ⋯
, opt for a more streamlined approach like
return ({ get a() { return f(); }, get b() { return g(); ⋯ }})[k]
. This method signifies "index into this object" rather than "check lots of cases."

Here is an example implementation:

interface LengthTypeValue {
  [LengthTypeEnum.SELECT]: { lengths: number[] };
  [LengthTypeEnum.STATIC]: { length: number };
  [LengthTypeEnum.CONDITION]: { conditions: any[] };
  [LengthTypeEnum.PERIOD]: { length_min: number; length_max: number };
}

type LengthType<K extends LengthTypeEnum> =
  { [P in K]: { length_type: P } & LengthTypeValue[P] }[K];

function initializeLengthData<T extends LengthTypeEnum>(length_type: T): LengthType<T> {
  const l: LengthTypeValue = {
    get [LengthTypeEnum.SELECT]() {
      return { lengths: [] }
    },
    get [LengthTypeEnum.PERIOD]() {
      return { length_min: 0, length_max: 0 }
    },
    get [LengthTypeEnum.STATIC]() {
      return { length: 0 }
    },
    get [LengthTypeEnum.CONDITION]() {
      return { conditions: [] }
    }
  }
  return { length_type, ...l[length_type] };
}

A new type LengthTypeValue is defined that maps LengthTypeEnum to different parts of LengthType<>, utilizing the getter trick for improved efficiency. Additionally, LengthType is revamped to incorporate a generic indexed access approach, ensuring distribution across various types. The resulting acceptance is achieved through l[length_type] of type LengthTypeValue[T], which upon spreading yields the intersection from LengthType<T>.

Playground link to 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

Not all generic types specified with an extends clause are appropriately narrowed down as expected

Consider the following scenario: type MyType = 'val1' | 'val2' | 'val3'; const variable = 'val1' as MyType; const val2 = 'val2'; const val3 = 'val3'; declare function test<U extends MyType&g ...

Issue encountered while utilizing combineReducers: "Error: The assetsReducer returned an undefined value during initialization."

Issue: The "assetsReducer" has returned an undefined value during initialization. When the state passed to the reducer is undefined, it must explicitly return the initial state, which cannot be undefined. If no value is set for this reducer, consider using ...

Differences between Angular components and TypeScript classes in models

In my observation, I have noticed that in several instances, manual models are being created for components to specifically manage the data. Despite this, the component already contains a ts class along with the html and css data. Shouldn't the task ...

Typescript with Angular: Despite having 7 values in the map, Map.get is returning undefined

Why does Map.get always return undefined when using a number from a form element (extra1) in this code snippet? extraById = new Map<number,Extra>(); @Input() extra1: number = -1; formChanged(carConfigurationFormChanged : any) { const index ...

Struggling with setting up eslint in my typescript project

Below is the contents of my package.json file: { "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.13.0", "airbnb": "^0.0.2&qu ...

Ways to change a value into int8, int16, int32, uint8, uint16, or uint32

In TypeScript, the number variable is floating point by default. However, there are situations where it's necessary to restrict the variable to a specific size or type similar to other programming languages. For instance, types like int8, int16, int32 ...

The 'required' validator in Mongoose seems to be malfunctioning

I've been attempting to validate the request body against a Mongoose model that has 'required' validators, but I haven't been successful in achieving the desired outcome so far. My setup involves using Next.js API routes connected to Mo ...

Issue with Promise not resolving in Node when using Edge

As I explore the best way to utilize my C# dlls with Edgejs for Node, I encountered a situation where one proxy function in Node appears like this (a class method in Typescript): readSettings(args: ReadSettingsParams) : Promise<response> { let $ ...

How come my uploaded Excel Javascript add-on opens in an external browser instead of the task pane?

Note: It has come to my attention that I must save the taskpane.html file on my local drive before it opens in an external browser. This detail slipped my notice last week. I am currently developing a Javascript, or rather Typescript, API add-in for Excel ...

Utilizing Angular and Typescript for Enhanced Modal Functionality: Implementing Bootstrap Modals in Various Components

When working in Angular, I have a component called Modal. I need to open the same Modal Component from two different places. The catch is, I want the button text in the Banner image to say "Get Started Now". Check out the Image linked below for reference. ...

Implementing GetServerSideProps with Next-Auth: Error message - Trying to destructure property 'nextauth' from 'req.query' which is undefined

I encountered an issue while using the getServerSideProps function in Next.js with Next-Auth. The error I received was a TypeError: TypeError: Cannot destructure property 'nextauth' of 'req.query' as it is undefined. Upon checking with ...

Exporting a class from an index.ts file may result in a problem where the injected constructor is

Utilizing an index.ts file to manage exports, following the guidelines outlined in the Angular 2 style guide (https://github.com/mgechev/angular2-style-guide/blob/master/old/README.md#directory-structure), has been successful throughout my application deve ...

The parameter type '==="' cannot be assigned to the 'WhereFilterOp' type in this argument

I'm currently working on creating a where clause for a firebase collection reference: this.allItineraries = firebase .firestore() .collection(`itinerary`); Here is the issue with the where clause: return this.allItiner ...

Having issues with NGXS subscription not functioning properly when selecting a variable

Currently, I am working with Angular 11 and NGXS. One issue I am facing involves a subscription for a variable in the state. Here is the problematic subscription: @Select(state => state.alert.alerts) alerts$: Observable<any[]> ngOnInit(): void { t ...

The argument of type 'NextRouter' cannot be assigned to the parameter of type 'Props' in this scenario

In my component, I am initializing a Formik form by calling a function and passing the next/router object. This is how it looks: export default function Reset() { const router = useRouter(); const formik = useFormik(RecoverForm(router)); return ( ...

Angular 16 routing not loading content

I configured the routes in Angular v16 and encountered a blank page issue with the login and register functionality. I suspect it has to do with routing, but after checking multiple times, I couldn't pinpoint the exact problem. Below are snippets of t ...

Create a Typescript index signature that incorporates individual generic types for each field

Many times, the keys of a record determine its value. For instance: const record = { [2]: 5, ["string"]: "otherString", ["there is"]: "a pattern" } In these instances, each key of type K corresponds to the ...

What is the best way to retrieve a variable that has been exported from a page and access it in _

Suppose this is my pages/visitor.tsx const PageQuery = 'my query'; const Visitor = () => { return <div>Hello, World!</div>; }; export default Visitor; How can I retrieve PageQuery in _app.tsx? One approach seems to be by assi ...

Creating adaptable Object Properties using Zod

Just dipping my toes into Typescript, Zod, and Trpc. Let's say I have a schema for animals and plants. I want to keep all their shared properties in the main part of the schema, while putting more specific details into a sub-object named custom. (jus ...

Exploring the communication between two components in Angular 2

My Angular components include: Create-Articles: used for creating articles. List Articles: utilized for listing all articles. The parent component is the Home Component. import { Component, OnInit } from '@angular/core'; @Component({ ...