What is the best way to create a universal limitation for a larger collection of a discriminated union?

Is it possible to enforce that when defining a generic class Foo<X>, where X represents a discriminated union type, X must be a superset of another discriminated union Y?

In my specific scenario, I am utilizing a discriminated union to differentiate between various action types. In the context of an application using Redux, actions are distinct types with different payloads, and the Redux reducer is capable of accepting any action. Thus, I use a discriminated union of action types to describe the action parameter.

In the provided example, reminiscent of my actual issue, there is an extensible base class that can handle BaseActionTypes. My goal is to pass in ExtendedTypes as the generic parameter.



interface Run {

}

interface Walk {

}



type BaseActionTypes = Run | Walk

interface Jump {

}

type ExtendedActionTypes = BaseActionTypes | Jump;

class ActionDoer<ActionTypes extends BaseActionTypes> {

    doAction(a: ActionTypes) {

    }

    walk() {
        const w: Walk = {};
        this.doAction(w); // ERROR!
    }

}

class ExtendedActionDoer extends ActionDoer<ExtendedActionTypes> {
}

const extendedActionDoer = new ExtendedActionDoer();
const j: Jump = {};
extendedActionDoer.doAction(j);

Playground Link

The error generated by my code is:

Argument of type 'Walk' is not assignable to parameter of type 'ActionTypes'.
  'Walk' is assignable to the constraint of type 'ActionTypes', but 'ActionTypes' could be instantiated with a different subtype 
of constraint 'BaseActionTypes'.(2345)

I'm uncertain why the base ActionDoer cannot execute doAction(w) here. How can I ensure that whatever union of actions passed in as ActionTypes includes at least the set of actions in the union

BaseActionTypes</code, potentially consisting of additional actions? Or, put differently, how can I specify that <code>ActionTypes
must be a superset of BaseActionTypes for two discriminated union types X and Y?

Answer №1

In this scenario, the use of `extend` with generic types indicates that our class will ultimately contain one or more members from the set of possible members defined by `BaseActionTypes`. The `extends` keyword in union types signifies that the type can be assigned any value that is a subset of its own set of values - not more, not less. Therefore, the issue arises when trying to assign a value of type `ExtendedActionTypes`, which has an additional option, beyond what `BaseActionTypes` permits.

So, it's clear that `ExtendedActionTypes` cannot be directly assigned to `BaseActionTypes` due to the presence of extra options.

Your specific error stems from attempting to assign a value of type `Walk` to a variable where `ActionTypes extends BaseActionTypes`. Since `ActionTypes` could potentially lack the `Walk` member, such assignment is invalid and results in an error. You can see this demonstrated here:

// no error as `Run` extends `BaseActionTypes`
class ExtendedActionDoer extends ActionDoer<Run> {
}

The above example shows that `Walk` cannot be substituted for `Run`.

To rectify this issue, you could consider removing the constraint altogether:

type ExtendedActionTypes = BaseActionTypes | Jump;

class ActionDoer {
    doAction<ActionType extends BaseActionTypes>(a: ActionType) {
    }
    walk() {
        const w: Walk = {type: 'Walk'};
        this.doAction(w);
    }
}

class ExtendedActionDoer extends ActionDoer {
    doAction(a: ExtendedActionTypes) {
    }
}

const extendedActionDoer = new ExtendedActionDoer();
const j: Jump = {type: 'Jump'};
extendedActionDoer.doAction(j);

You can also maintain the generic approach while enforcing the existence of certain properties:

type ExtendedActionTypes = Jump;

class ActionDoer<ActionType> {
    doAction(a: ActionType | BaseActionTypes) {
    }
    walk() {
        const w: Walk = {type: 'Walk'};
        this.doAction(w);
    }
}

class ExtendedActionDoer extends ActionDoer<ExtendedActionTypes> {
    doAction(a: ExtendedActionTypes) {
    }
}

const extendedActionDoer = new ExtendedActionDoer();
const j: Jump = {type: 'Jump'};
extendedActionDoer.doAction(j);

The key takeaway here is the flexibility provided by `doAction(a: ActionType | BaseActionTypes)`, ensuring that any input is accepted as long as it contains members of `BaseActionTypes`.

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

Converting md ElementRef to HtmlElement in Angular 2+: A Step-by-Step Guide

My query is related to retrieving the favorite food input in TypeScript. The input field is defined as: <input mdInput #try placeholder="Favorite food" value="Sushi"> In my TypeScript file, I have accessed this input using: @ViewChild('try ...

What is the correct way to invoke a function that accepts a combination of specific string values when all I have is a regular string?

Within the TypeScript function declaration provided below, the parameter type alignment consists of unioned literals. function printText(s: string, alignment: "left" | "right" | "center") { // ... } As per the documentation ...

Access PDF document in a fresh tab

How can I open a PDF file in a new tab using Angular 6? I have tried the following implementation: Rest controller: @RestController @RequestMapping("/downloads") public class DownloadsController { private static final String EXTERNAL_FILE_PATH = "/U ...

Can someone please explain how to display a specific element from a JSON Array?

Is there a way to display only this specific part? /db/User_DataDb/61500546-4e63-42fd-9d54-b92d0f7b9be1 from the entirety of this Object obj.sel_an: [ { "__zone_symbol__state":true, "__zone_symbol__value":"/db/User_DataDb/61500546-4 ...

Guide to creating a personalized pipe that switches out periods for commas

I currently have a number with decimal points like --> 1.33 My goal is to convert this value so that instead of a dot, a comma is displayed. Initially, I attempted this using a custom pipe but unfortunately, it did not yield the desired result. {{get ...

Error message in Visual Studio 2017: Identical name 'URLs' declared twice in

In our Visual Studio 2017 project, we have multiple TypeScript files that define a URLs class. Each file contains different implementations of the class to change site URLs based on the specific use case: customer/urls.ts namespace Portal { export cl ...

Issue encountered with NextJS where the post request utilizing Bcrypt is not being recognized

In the process of developing a basic login feature using nextJS, I have successfully managed to save new usernames and encrypted passwords from the registration page. The login functionality is intended to be similar, but requires comparing the password st ...

Issue Error: NG0201: NgControl provider not found within NodeInjector

My creativity has hit a roadblock and I'm looking for some help. I decided to use the Reactive Forms Module in Angular, so I imported it into my app.module.ts as shown below: import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ ...

Download pictures from swift into typescript with the help of share extensions

Currently, I am working on setting up Share Extensions in my ionic3 application. To begin with, I followed these steps: Firstly, I built the app and then launched it in Xcode. After that, I added a Share Extension by navigating to File -> New -> Ta ...

How can I make TypeScript mimic the ability of JavaScript object wrappers to determine whether a primitive value has a particular "property"?

When using XMLValidator, the return value of .validate function can be either true or ValidationError, but this may not be entirely accurate (please refer to my update). The ValidationError object includes an err property. validate( xmlData: string, opti ...

Troubles encountered when trying to execute mocha within Firebase functions

My latest project involved developing a Node/Typescript app that interacted with data from Firebase Cloud Firestore. The app performed flawlessly, and I conducted endpoint testing using simple mocha commands on the generated .js file. Below is an example o ...

Ways to modify the CSS of an active class within a child component when clicking on another shared component in angular

In my HTML template, I am encountering an issue with two common components. When I click on the app-header link, its active class is applied. However, when I proceed to click on the side navbar's link, its active class also gets applied. I want to en ...

While utilizing the imodel.js front-end for designing a custom geometric model, I ran into an issue while trying to display it

Utilizing imodel.js front-end, I was able to design a customized geometric model featuring elements like a collection box. However, when placing the model within the existing SpatialViewState in bim, it failed to display properly in the current view. Sub ...

React, Typescript, and Material-UI 4 compilation dilemma

Out of the blue, my entire project collapsed and I can't seem to build it. I recently reset the project using a fresh create-react app build, which seemed fine initially. However, just yesterday, I encountered a similar issue but with a different erro ...

Proper method of managing undeclared declaration files (index.d.ts)

I encountered the following error message: error TS7016: Could not find a declaration file for module 'react-native-camera'. '/Users/ilja/Documents/Repositories/blok/node_modules/react-native-camera/index.js' implicitly has an 'an ...

TypeScript: creating an interface property that relies on the value of another

Is it feasible to have an interface property that relies on another? For instance, consider the following: const object = { foo: 'hello', bar: { hello: '123', }, } I wish to ensure that the key in bar corresponds to the value of f ...

Learn the art of bypassing TypeScript errors using @ts-ignore!

I recently encountered an issue when trying to import a pure JavaScript library into a TypeScript project, resulting in the error message: Could not find a declaration file for module xxx. After some research, I learned that this error can be suppressed u ...

"I am looking to retrieve the properties of an object that belongs to the EChartsOption type in TypeScript when working with Angular and ECharts. How

Currently, I am exploring how to access a property of an EChartOptions object in Angular 16.0.2, which may be undefined as I am still new to TypeScript. List of npm packages: eapp/src$ npm list <a href="/cdn-cgi/l/email-protection" class="__cf_email__" ...

TypeScript failing to infer type from return value of class method

Currently, I am developing a class where the constructor calls one of its methods toJSON and sets the return value to an instance property: class Example { property; snapshot; constructor(){ this.property = 'property' if (Math.ran ...

The TypeScript compiler is generating node_modules and type declaration files in opposition to the guidelines outlined in the tsconfig.json file

For the past week, I've been trying to troubleshoot this issue and it has me completely puzzled. What's even more puzzling is that this app was compiling perfectly fine for months until this problem occurred seemingly out of nowhere without any c ...