Differentiating functions in Typescript through method overloading by inferring the second argument's type based on the first argument's

Looking to implement method overloading in Typescript with stricter type checking for method invocation based on different arguments. I am encountering an error:

'props' is possibly 'undefined'.

This is the code that is causing the error (playground):

export default class ControlsContr {
  // constructor(public state: Signal<State> = signal(State.Idle)) {}

  dispatch(ev: Event.Reset): void;
  dispatch(ev: Event.Delete, props: { id: number }): void;
  dispatch(ev: Event, props?: { id?: number }): void {
    if (ev === Event.Delete) {
      const num: number = props.id;
    }
  }
}

export enum State {
  Idle = "Idle",
  Deleting = "Deleting",
  DeleteDone = "DeleteDone",
}

export enum Event {
  Delete = "Delete",
  Reset = "Reset",
}

function deleteItem(id: number) {
}

Is it possible to achieve what I'm attempting and if so, how can it be done?

Answer №1

Because of the interconnected parameters in the overload signatures, a corresponding implementation signature must be written to maintain this correlation. One way to achieve this is by using a notation that depicts the parameters as a tuple with destructured rest parameters, forming a union type that mirrors the pairings present in the overload signatures. By doing so, the compiler can accurately infer based on the existing discriminant conditional. The following example illustrates this concept:

TS Playground

class ControlsContr {
  dispatch(ev: Event.Reset): void;
  dispatch(ev: Event.Delete, props: { id: number }): void;
  dispatch(
    ...[ev, props]: [ev: Event.Reset, undefined?] | [
      ev: Event.Delete,
      props: { id: number },
    ]
  ) {
    if (ev === Event.Delete) {
      ev
    //^? (parameter) ev: Event.Delete
      props
    //^? (parameter) props: { id: number }

      const num = props.id; // ok
          //^? const num: number
    } else {
      ev
    //^? (parameter) ev: Event.Reset
      props
    //^? (parameter) props: undefined
    }
  }
}

Answer №2

Typescript's documentation explains how we can utilize literal types to refer to specific strings and numbers in type positions, allowing for functions that only accept a certain set of known values.

Enums cannot achieve this, but strings can be used instead:

export default class ControlsContr {
  dispatch(ev: "Reset"): void;
  dispatch(ev: "Delete", props: { id: number }): void;
  dispatch(ev: Event, props?: { id?: number }): void {}
}

export type Event = "Delete" | "Reset"

const controls = new ControlsContr()
controls.dispatch("Reset")
controls.dispatch("Delete", {id: 1})
// controls.dispatch("Reset", {id: 1}) fails
// controls.dispatch("Delete") fails

Alternatively, discriminating unions can be used:

export default class ControlsContr {
  dispatch(ev: Event): void {
    if (ev.action == "Delete") {
      console.log(ev.id)
    }
  }
}

type ResetEvent = {action: "Reset"};
type DeleteEvent = {action: "Delete", id: number};
type Event = ResetEvent | DeleteEvent

const controls = new ControlsContr()
controls.dispatch({action: "Reset"})
controls.dispatch({action: "Delete", id: 1})

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 the best way to organize information in a table based on the date

This is my data table https://i.stack.imgur.com/1DNlj.png in the displayed table, the registration dates are sorted with the oldest date at the top. However, I aim to have the newest data displayed first. Below is the code snippet I am using: this.dataSo ...

Identifying collisions or contact between HTML elements with Angular 4

I've encountered an issue that I could use some help with. I am implementing CSS animations to move p elements horizontally inside a div at varying speeds. My goal is for them to change direction once they come into contact with each other. Any sugges ...

The mat-slide-toggle component does not recognize the checked binding

My angular app contains the mat-slide-toggle functionality. switchValue: {{ switch }} <br /> <mat-slide-toggle [checked]="switch" (toggleChange)="toggle()">Toggle me!</mat-slide-toggle> </div> This is how the ...

Seeking a breakdown of fundamental Typescript/Javascript and RxJs code

Trying to make sense of rxjs has been a challenge for me, especially when looking at these specific lines of code: const dispatcher = fn => (...args) => appState.next(fn(...args)); const actionX = dispatcher(data =>({type: 'X', data})); ...

Error: Null is causing an issue and preventing property 'isSkipSelf' from being read in Angular7

While assembling the package for my module, I encountered the following error. TypeError: Cannot read property 'isSkipSelf' of null at ProviderElementContext._getDependency(C:\Users\ravinder\MyProjectName\node_modules\@ ...

The final value of a loop is consistently returned each time

Creating a loop to generate objects. Action.ts public campaing:any = { 'id': '', 'campaing_code': '', 'campaing_type': '', 'start_date': this.currentDate, 'end ...

When clicking initially, the default input value in an Angular 2 form does not get set

I am currently learning angular2 as a beginner programmer. My goal is to create a form where, upon clicking on an employee, an editable form will appear with the employee's current data. However, I have encountered an issue where clicking on a user f ...

Using TypeScript to work with JSON fields that include the '@' symbol

While working on an Angular project with TypeScript, I am facing a challenge of displaying certain JSON fields obtained from a POST request to the user. One of the fields begins with the '@' symbol, which is a reserved word in Angular causing th ...

How can I retrieve properties from a superclass in Typescript/Phaser?

Within my parent class, I have inherited from Phaser.GameObjects.Container. This parent class contains a property called InformationPanel which is of a custom class. The container also has multiple children of type Container. I am attempting to access the ...

Enhancing State Management with CombineReducers in TypeScript

export const rootReducer = combineReducers({ login: loginReducer, }); Everything is working fine with the current setup, but I encountered an issue when attempting to combine another reducer: export const rootReducer = combineReducers({ login: lo ...

Implementing Angular 2 - Steps to ensure a service is accessible within the app module

I'm running into an issue trying to utilize a function within a service that I believed was globally accessible. The service in question is named SavedNotificationService: import { Injectable } from '@angular/core'; @Injectable() export cl ...

What factors contribute to TypeScript having varying generic function inference behaviors between arrow functions and regular functions?

Consider the TypeScript example below: function test<T = unknown>(options: { a: (c: T) => void, b: () => T }) {} test({ a: (c) => { c }, // c is number b: () => 123 }) test({ b: () => 123, a: (c) => { retur ...

Error Encountered when Trying to Utilize Vue Component Getter in TypeScript Mixin (Error Code: TS233

Here is the code snippet that I am currently working with: import {Component, Vue} from 'vue-property-decorator'; @Component({}) export default class MyMixin extends Vue { scrollToTop(): void { let scrollingWrapper: any = (this.$refs[th ...

Oops! It seems like there is an issue with reading the property 'filter' of an undefined object. Any ideas on how to resolve this error

Having an issue with a custom filter that is causing an error "ERROR TypeError: Cannot read property 'filter' of undefined". I need help fixing this as it's preventing anything from rendering on the page. Any suggestions on what changes I sh ...

Using Typescript: How to access a variable beyond the scope of a loop

After creating an array, I need to access the elements outside of the loop. I am aware that they are not in the scope and using 'this.' before them does not grant access. colIdx = colIdx + this.columns.findIndex(c => c.editable); this.focusIn ...

What is the best way to convert an array of data into a dataset format in React Native?

Within my specific use case, I am seeking to reform the array structure prior to loading it into a line chart. In this context, the props received are as follows: const data = [26.727, 26.952, 12.132, 25.933, 12.151, 28.492, 12.134, 26.191] The objective ...

Getting object arguments from an npm script in a NodeJS and TypeScript environment can be achieved by following these steps

I'm trying to pass an object through an NPM script, like this: "update-user-roles": "ts-node user-roles.ts {PAID_USER: true, VIP: true}" My function is able to pick up the object, but it seems to be adding extra commas which is ...

No data is generated when choosing from the dropdown menu in mat-select

I have a select function where all options are selected, but the selected sections are not shown. When I remove the all select function, everything works fine. Can you help me find the error? Check out my work on Stackblitz Here is my code: Select <m ...

Best Practices for Integrating Angular with Your Custom JavaScript Library

Imagine needing to create a TypeScript function that can be utilized across various components, services, or modules. For example, let's say you want an alert wrapper like this: my_alert(msg); // function my_alert(msg) { alert(msg); } You might hav ...

How to dynamically generate Angular component selectors with variables or loops?

Looking to dynamically generate the Selector Tag in my app.component.html using a variable. Let's say the variable name is: componentVar:string What I want in my app.component.html: <componentVar></componentVar> or <app-componentVar& ...