Using Generic Types in TypeScript for Conditional Logic

To better illustrate my goal, I will use code:

Let's start with two classes: Shoe and Dress

class Shoe {
    constructor(public size: number){}
}
class Dress {
    constructor(public style: string){}
}

I need a generic box that can hold either a Shoe or a Dress (but not both):

class Box <T extends Shoe | Dress > {
}

Next, there are utility classes for handling the movement of shoes and dresses:

class ShoeMover {
    constructor(public size: number[]){}
}

And another utility class for packing dresses:

class DressPacker {
    constructor(public style: string[]){}
}

Now, we create a generic mover class that works with either Shoes or Dresses:

class Move<B extends Box<Shoe> | Box<Dress>> {
    private box: B;
    constructor(toMove: B) {
        this.box = toMove;
    }
    public mover(tool: ShoeMover | DressPacker) {
    }
}

The challenge is to ensure that if Move is instantiated with Box<Shoe>, only ShoeMover is accepted, and vice versa. This way, only compatible tools can be used:

let shoemover = new Move(new Box<Shoe>());

// This should compile
shoemover.mover(new ShoeMover([21]))

// This should not compile, but currently does
shoemover.mover(new DressPacker(["1"]))

Several attempts were made using conditional types, but none provided the desired compile-time guarantees. Any suggestions on how to achieve this?

Edit.

The solution mentioned above works in one scenario but fails in another when the constructor of Move takes a union type as input.

type Mover<T> = 
  T extends Shoe ? ShoeMover : 
  T extends Dress ? DressPacker : 
  never; 

class Move<T extends Shoe | Dress> {
    private box: Box<T>;
    constructor(public toMove: Box<Shoe>[] | Box<Dress>[]) {
        this.box = toMove;
    }
    public mover(tool: Mover<T>) {
    }
}


let shoemover = new Move(new Array<Box<Shoe>>());

// This should compile
shoemover.mover(new ShoeMover([21]))

// This should not compile, but currently does
shoemover.mover(new DressPacker(["1"]))

Playground Link

Answer №1

Just a little adjustment needed to make it work perfectly. You should utilize generics in the mover method as well, or else it won't recognize what T stands for. Think of the generic type as a method that requires a generic T as an argument, and <> as ():

type Mover<T> = 
  T extends Shoe ? ShoeMover : 
  T extends Dress ? DressPacker : 
  never; 

class Move<T extends Shoe | Dress> {
    private box: Box<T>;
    constructor(toMove: Box<T>) {
        this.box = toMove;
    }
    public mover(tool: Mover<T>) {
    }
}

In addition, I made a slight modification to the Move definition by removing the Box generic since you can easily include it within the inner definitions of the class. However, your initial solution would still function with:

type MoverFromEitherShoeOrDressA<T> =
    T extends Box<infer U> ?
        U extends Shoe ? ShoeMover :
        U extends Dress ? DressPacker :
        never:
    never;

public mover(tool: MoverFromEitherShoeOrDressA<B>) { // <-- Here
}

Edit: playground available here: Playground Link

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 causing the consistent occurrences of receiving false in Angular?

findUser(id:number):boolean{ var bool :boolean =false this.companyService.query().subscribe((result)=>{ for (let i = 0; i < result.json.length; i++) { try { if( id == result.json[i].user.id) ...

"Utilizing jQuery and Bootstrap 4 in TypeScript, attempting to close modal window using jQuery is not functioning

Trying to make use of jquery to close a bootstrap modal within an angular project using typescript code. The following is the code: function call in html: (click)="populaterfpfromsaved(i, createSaved, createProp)" createSaved and createProp are local ...

What is the reason for needing to specify event.target as an HTMLInputElement?

I am currently working on a codebase that uses Material Ui with theme overrides. As I set up my SettingContext and SettingsProvider, I find myself facing some syntax that is still unclear to me. Let's take a look at the following code snippet: const ...

Typescript navigation and Next.js technology

Currently, I am in the process of learning typescript and attempting to create a navigation bar. However, I encountered an error message stating "Unexpected token header. Expected jsx identifier". I am a bit puzzled by this issue. Could someone kindly pro ...

A declaration file in Typescript does not act as a module

Attempting to create a TypeScript declaration file for a given JavaScript library my_lib.js : function sum(a, b) { return a + b; } function difference(a, b) { return a - b; } module.exports = { sum: sum, difference: difference } my_lib.d.ts ...

What is the best way to convert a recordset to an array using React?

I'm attempting to create an array by retrieving data from a SQL recordset: +------------+------------+------------+ | start_type | field_name | start_text | +------------+------------+------------+ | 0 | Field1 | some text. | +----------- ...

The RxJS observable fails to initiate the subscribe function following the mergeMap operation

I am attempting to organize my dataset in my Angular application using the RxJS operators and split it into multiple streams. However, I am facing difficulties making this work properly. Inside my SignalRService, I have set up a SignalR trigger in the cons ...

Need to end all Node.js instances to properly reflect the code. Any solutions for resolving this issue?

After developing an application using typescript, hapi, and nodejs, I encountered a strange issue. Whenever I save, remove, or add new code, the changes are not reflected even after running gulp build. The only way to get it working is by closing all run ...

Updating the main window in Angular after the closure of a popup window

Is it possible in Angular typescript to detect the close event of a popup window and then refresh the parent window? I attempted to achieve this by including the following script in the component that will be loaded onto the popup window, but unfortunatel ...

Strategies for effectively choosing this specific entity from the repository

Is it possible to choose the right entity when crafting a repository method using typeorm? I'm facing an issue where I need to select the password property specifically from the Admin entity, however, the "this" keyword selects the Repository instead ...

Converting HTML templates into AMD modules using grunt-ts

When using the grunt-ts plugin and specifying the html: ["*.tpl.html"] option, it converts *.tpl.html files into *.tpl.html.js files at the end of the process, creating a global var. Is there a way to configure grunt-ts to output the final .js file in a d ...

Tips for integrating Typescript into a pre-existing Vue 3 project

The contents of my package.json file are as follows: { "name": "view", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve" ...

Tips for including a sequelize getter in a model instance?

I'm currently struggling to add a getter to the name field of the Company model object in my project. Despite trying various approaches, I haven't had any success so far. Unfortunately, I also couldn't find a suitable example to guide me thr ...

Instructions for implementing personalized horizontal and vertical scrolling within Angular 9

I am currently working on an angular application where users can upload files, and I display the contents of the file on the user interface. These files may be quite long, so I would need vertical scrolling to navigate through them easily. Additionally, fo ...

Bidirectional communication linking an Angular 2 component and service utilizing the power of Observables

I'm having difficulties establishing a simple connection between an Angular 2 component and service using Observable. I've been trying to achieve this, but I can't seem to get it right. Here's the scenario: My component HeroViewerCompo ...

Tips for resolving the setAttribute() function error message: "Argument of type 'boolean' is not assignable to parameter of type 'string'"

I am currently working on a function that dynamically updates the HTML aria-expanded attribute based on whether it is true or false. However, when I declare element as HTMLElement, I encounter an error stating Argument of type 'boolean' is not as ...

In Typescript, which kind of event is suitable for MouseEvent<HTMLDivElement>?

My goal is to close the modal when clicking outside the div element. Take a look at my code below. // The onClose function is a setState(false) function. import { useRef, useEffect } from 'hooks' import { MouseEvent } from 'react' imp ...

Eliminate duplicated partial objects within a nested array of objects in TypeScript/JavaScript

I'm dealing with a nested array of objects structured like this: const nestedArray = [ [{ id: 1 }, { id: 2 }, { id: 3 }], [{ id: 1 }, { id: 2 }], [{ id: 4 }, { id: 5 }, { id: 6 }], ] In the case where objects with id 1 and 2 are already grou ...

Is the child constantly updating due to a function call?

Having difficulty navigating the intricacies where a child keeps re-rendering due to passing a function from the parent, which in turn references an editor's value in draftjs. function Parent() { const [doSomethingValue, setDoSomethingValue] = Re ...

I'm having trouble viewing the unique Google Map design on my application

I have recently customized Google maps following the guidelines in this documentation: https://developers.google.com/maps/documentation/javascript/styling For styling, I utilized the Cloud tool and opted for the available template instead of using JSON st ...