What is the correct way to define a function signature that accepts parameters with union types?

One challenge I am facing is creating an API that requires a function with a parameter that can be either type A or B. To address this issue, I have been utilizing interfaces for typing these parameters. However, the problem arises as these types do not share any common members. The compiler keeps throwing errors about missing properties.

export interface MyTypeA {
  prop1: string;
  prop2: boolean;
} 
export interface MyTypeB {
  prop3: number;
  prop4: string;
}

doSomething(param1: string, param2: MyTypeA | MyTypeB){
  switch(param1){
    case 'a':
    case 'b': {
      const cf = this.resolver.resolveComponentFactory(MyClassAComponent);
      const component = this.wrap.createComponent(cf);
      component.instance.prop1 = param2.prop1;
      component.instance.prop2 = param2.prop2;
      break;
    }
    case 'c': {
      const cf = this.resolver.resolveComponentFactory(MyClassBComponent);
      const component = this.wrap.createComponent(cf);
      component.instance.prop3 = param2.prop3;
      break;
    }

  }
}

I am considering whether switching to type instead of interface might provide a solution, but I am unsure of how to proceed in doing so.

Answer №1

There are 3 potential solutions:

  • Ensure each individual use of param2 is casted.
  • Utilize Tagged Unions (also known as discriminated unions or algebraic data types).
  • Implement User Defined Type Guards.

If combining param1 and param2 makes sense to you, Opt for Tagged Unions. Otherwise, go with User Defined Type Guards.

👉 Cast each single usage of param2:

This approach might become too long-winded if used extensively, but it's the most direct method:

...

component.instance.prop1 = (param2 as MyTypeA).prop1;
component.instance.prop2 = (param2 as MyTypeA).prop2;

...

Employing this solution won't introduce extra code; the cast will be entirely removed from the generated code.

👉 Utilize Tagged Unions (also known as discriminated unions or algebraic data types):

You can merge param1 and param2, converting your custom types to tagged unions:

export interface MyTypeA {
  param1: 'a' | 'b';
  prop1: string;
  prop2: boolean;
}

export interface MyTypeB {
  param1: 'c';
  prop3: number;
  prop4: string;
}

doSomething(param2: MyTypeA | MyTypeB) {
  switch(param2.param1) {
    case 'a':
    case 'b': {
      // Compiler identifies param2 as type MyTypeA due to its param1 property being 'a' or 'b'.

      const cf = this.resolver.resolveComponentFactory(MyClassAComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop1 = param2.prop1;
      component.instance.prop2 = param2.prop2;

      break;
    }

    case 'c': {
      // Compiler identifies param2 as type MyTypeB because its param1 property is 'c'.

      const cf = this.resolver.resolveComponentFactory(MyClassBComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop3 = param2.prop3;

      break;
    }
  }
}

This method won't add additional code when generated; the interfaces, including the tagged param, won't appear in the created code.

👉 Implement User Defined Type Guards:

You can narrow down the type of param2 using User Defined Type Guards:

export interface MyTypeA {
  prop1: string;
  prop2: boolean;
}

export interface MyTypeB {
  prop3: number;
  prop4: string;
}

function isA(arg: any): arg is MyTypeA {
    return arg.hasOwnProperty('prop1');
}

function isB(arg: any): arg is MyTypeB {
    return arg.hasOwnProperty('prop3');
}

doSomething(param1: string, param2: MyTypeA | MyTypeB) {
  switch(param1) {
    case 'a':
    case 'b': {
      if (!isA(param2)) return;

      // Compiler recognizes param2 as type MyTypeA:

      const cf = this.resolver.resolveComponentFactory(MyClassAComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop1 = param2.prop1;
      component.instance.prop2 = param2.prop2;

      break;
    }

    case 'c': {
      if (!isB(param2)) return;

      // Compiler recognizes param2 as type MyTypeB:

      const cf = this.resolver.resolveComponentFactory(MyClassBComponent);
      const component = this.wrap.createComponent(cf);

      component.instance.prop3 = param2.prop3;

      break;
    }
  }
}

Note that this method will result in additional code generation - the functions isA and isB, along with their calls, will be included in the output 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

Despite strongly typed manipulations, the type of a Typescript variable remains unchanged

Enhanced TS Playground Example Script type Base = { name: string } type ExtA = { address: string } type ExtB = { streetName: string; } function handler<T extends Base>(input: T): T {return input} /** how can I create a composite object? */ ...

Issues with function passing in child components are causing problems in ReactJS when using Typescript

I have a scenario where I am passing a function from the parent component to a child component. This function is responsible for updating a value in the state of the parent component. The passing of the function works smoothly, and in the child component, ...

Creating a type or interface within a class in TypeScript allows for encapsulation of

I have a situation where I am trying to optimize my code by defining a derivative type inside a generic class in TypeScript. The goal is to avoid writing the derivative type every time, but I keep running into an error. Here is the current version that is ...

Resizing an input image in Angular 2

I am looking to create a file upload feature using Angular2 that will upload the original image but display a resized version as a thumbnail preview. Prior to uploading, I need the image to be shown as a thumbnail below the file input field. Currently, th ...

Best practices for securely storing access tokens in React's memory

I'm on a quest to find a secure and efficient way to store my access token in a React application. First and foremost, I have ruled out using local storage. I don't see the need to persist the access token since I can easily refresh it when nece ...

Exploring Typescript for Efficient Data Fetching

My main objective is to develop an application that can retrieve relevant data from a mySQL database, parse it properly, and display it on the page. To achieve this, I am leveraging Typescript and React. Here is a breakdown of the issue with the code: I h ...

Troubleshooting issue with Vue3 - TS Custom State Management

Issue: I am facing a challenge in transferring data between two separate components - the main component and another component. Despite attempting to implement reactive State Management based on Vue documentation, the object's value remains unchanged ...

Having trouble accessing Vuex's getter property within a computed property

Can you help me troubleshoot an issue? When I call a getter inside a computed property, it is giving me the following error message: TS2339: Property 'dictionary' does not exist on type 'CreateComponentPublicInstance{}, {}, {}, {}, {}, Com ...

Add a React component to the information window of Google Maps

I have successfully integrated multiple markers on a Google Map. Now, I am looking to add specific content for each marker. While coding everything in strings works fine, I encountered an issue when trying to load React elements inside those strings. For ...

adjusting the scrollbar to be positioned at the top when using Material UI stepper component

While using the material ui stepper, I encountered an issue where the scroll bar remains static and hidden behind the step number header when I click on the "save and continue" button. I expect that upon clicking the button, the scroll bar should automatic ...

Steps to resolve the issue of receiving a warning while utilizing @babel/plugin-transform-typescript for compiling TypeScript code

Every time I compile TypeScript using @babel/plugin-transform-typescript, I encounter a warning. The issue seems to be caused by another plugin injecting "_class" without properly registering it in the scope tracker. If you are the creator of that plugin, ...

The page remains static even after selecting a child route using routerLink in Angular 2

Hello, I am facing an issue that I need help with. I am using a routerLink to navigate to a child route in my Angular application. Although the URL changes as expected, the view does not update to display the component associated with the active child rout ...

Inheritance of Generic Types in TypeScript

Could someone assist me in understanding what is incorrect with the code snippet provided here? I am still learning Typescript. interface ICalcValue { readonly IsNumber: boolean; readonly IsString: boolean; } interface ICalcValue<T> ex ...

Serialising and deserialising TypeScript types in local storage

I'm currently working on a Typescript application where I store objects using local storage for development purposes. However, I've run into some trouble with deserialization. Specifically, I have an object called meeting of type MeetingModel: ...

"Error encountered while executing a code snippet using Navalia in TypeScript

I have been attempting to execute this code snippet from https://github.com/joelgriffith/navalia but despite my efforts, I have not been able to get it running smoothly without encountering errors: navaliatest.ts /// <reference path="typings.d.ts" /&g ...

Issues with Array.filter method when used in asynchronous scenarios

I am currently working with Ionic2 / Angular2 utilizing typescript. I have encountered an issue while attempting to filter an Array. The scenario is as follows: let localTours = []; ... let newTours = dbTours.filter(x => localTours.indexOf(x) < 0); ...

TypeScript's type 'T' has the potential to be instantiated with any type, even if it is not directly related to 'number'

Let's say we have a function that takes a value (for example, number) and a callback function that maps that value to another value. The function simply applies the provided callback: function mapNumber<T>(value: number, mapfn: (value: number) = ...

Information stored in IndexedDB is not retained permanently

My journey to explore various web technologies (such as HTML, CSS, JavaScript) led me to create a simple web application. To enhance the functionality of my app, I integrated IndexedDB for data storage and operations like insert, update, get and delete. H ...

Having trouble with installing Typescript on a MacBook?

I have been attempting to follow the instructions provided on TypeScriptLang.org but for some reason, I am unable to successfully download typescript. This is what I have tried so far: mkotsollariss-MacBook-Pro:/ mkotsollaris$ which node /usr/local/bin/n ...

Is it necessary to store the input value as a string in order to allow for empty values in a numerical input field?

Is there a way to handle the issue when emptying an input field as shown below? durationScalar: number; ... durationScalar: 1 ... this.setState({ durationScalar: valueAsNumber }); ... <input name="durationScalar" type="number" value={this.state.d ...