Type of condition based on function parameter values

I am facing a challenge where I need to merge two separate Typescript methods into one with the same name getDevice. The first method only requires a number input to return a Device, or null if no device is found:

protected getDevice(deviceId: number): Device | null {
  const device = this.devicesService.getDevice(deviceId);
  if (device == null)
    console.warn(`Failed to retrieve device #${deviceId}.`);
  return device;
}

The second method takes two arguments. The first argument can be either a number (similar to the previous method) or a Device (the result of the previous method):

protected getDeviceAs<T extends DeviceType>(
  deviceOrId: Device | number,
  deviceType: (new (device: Device) => T),
): T | null {
  const device = typeof deviceOrId === 'number'
    ? this.devicesService.getDevice(deviceOrId)
    : deviceOrId as Device;
  if (device == null) {
    console.warn(`Failed to retrieve the device #${deviceOrId}.`);
    return null;
  }
  return new deviceType(device);
}

The combined method would look something like this:

protected getDevice<<T extends DeviceType>(
  deviceOrId: Device | number,
  deviceType: (new (device: Device) => T) | null = null,
): Device | T | null {
  let device: Device | null = null;
  // If deviceOrId is a number
  if (typeof deviceOrId === 'number') {
    device = this.devicesService.getDevice(deviceOrId);
    if (device == null) {
      console.warn(`Failed to retrieve device #${deviceOrId}.`);
      return null;
    }
    if (deviceType == null) return device;
  }

  // Implementing getDeviceAs functionality
  return new deviceType(device);
}

The tricky part is defining the proper types for the entire function:

  • The return type depends on the type of the deviceOrId argument:
    • If deviceOrId is a Device, then the result should definitely be T | null
    • If deviceOrId is a number, the result could be Device | T | null
  • The return type and deviceOrId are influenced by the deviceType argument:
    • If deviceType is null, then deviceOrId must be of type number and the return type is Device | null
    • If deviceType is of type (new (device: Device) => T), then the return type must be T | null

Is achieving this kind of complexity possible in Typescript? If so, how can it be done? Perhaps utilizing some function overloading techniques?

Answer №1

If you're looking for a simple fix to the issue at hand, consider using function overloads in TypeScript (reference). Start by defining your function signatures. In this particular scenario, it would look like this:

protected getDevice(deviceId: number): Device | null;
protected getDevice<T extends DeviceType>(
  deviceOrId: Device | number,
  deviceType: (new (device: Device) => T),
): T | null;

Subsequently, proceed with crafting the actual implementation logic for the function. Note that the implementation signature does not add another overload. Here is the complete code snippet:

// Overload 1:
protected getDevice(deviceId: number): Device | null;
// Overload 2:
protected getDevice<T extends DeviceType>(
  deviceOrId: Device | number,
  deviceType: (new (device: Device) => T),
): T | null;
// Implementation:
protected getDevice<T extends DeviceType>(
  deviceOrId: Device | number,
  deviceType: (new (device: Device) => T) | null = null,
): Device | T | null {
  let device: Device | null = null;
  if (typeof deviceOrId === 'number') {
    device = this.devicesService.getDevice(deviceOrId);
    if (device == null) {
      console.warn(`Failed to retrieve device #${deviceOrId}.`);
      return null;
    }
    if (deviceType == null) return device;
  }
  return new deviceType(device);
}

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

Steps for managing files in Ionic Native: creating, reading, and writing them

Struggling to find proper examples for file operations like creating, reading, and writing text or logs into a file? I've done a lot of research but haven't stumbled upon any suitable solutions. The examples provided in this link seem helpful, ho ...

Retrieve the desired destination URL in the CanActivate guard when lazily loading modules in Angular

I am facing an issue with retrieving the target URL in the canActivate guard. Even though I have set up preloadingStrategy: PreloadAllModules in RouterModule.forRoot, the url property of ActivatedRoute does not contain the path. Here are the contents of bo ...

Managing DOM elements within a Vue 3 template using Typescript

As I delve into the world of Vue 3 as a beginner, I encountered a challenge when it came to managing the DOM within Vue 3 templates. Let's take a look at the source code. MainContainer.vue <template> <div class="main-container" r ...

Getting a "function is not defined" error in Angular 6 while using it within a nested function

I'm facing an issue related to typescript, where the following code is causing trouble: private loadTeams = function(){ let token = sessionStorage.getItem('token'); if(token !== undefined && token !== null && token ...

The feature of "compile on save" is not functioning properly in my current Angular project

Yesterday I used the angular cli (ng new my-app) to create a new project, but unfortunately the "compile on save" option is not functioning properly. Interestingly, I have two projects on my computer and this feature works fine for one of them but not for ...

What determines the narrowing of a type when it is defined as a literal versus when it is returned from a function?

I'm really trying to wrap my head around why type narrowing isn't working in this scenario. Here's an example where name is successfully narrowed down: function getPath(name: string | null): "continue" | "halt" { if (n ...

Uncovering the perfect body proportions using Webpack and SystemJS

In the process of developing an Angular2 library that needs to work with both SystemJS and Webpack, I encountered a situation where I had to detect the height and width in pixels of the body tag to set dimensions for child tags. However, the behavior of An ...

Troubleshooting problems with contenteditable and input in Firefox and Safari on Angular 5

Currently, I am in the process of creating a table with cells that are editable. However, I am facing a challenge in updating the visual and typescript code simultaneously. I have explored various alternatives but unfortunately, none of them seem to work. ...

How can we use Angular Table to automatically shift focus to the next row after we input a value in the last cell of the current row and press the Enter key

When the last cell of the first row is completed, the focus should move to the next row if there are no more cells in the current row. <!-- HTML file--> <tbody> <tr *ngFor="let row of rows;let i=index;" [c ...

Implementing click event binding with various CSS styles in Angular

Is there a way to attach multiple css classes to the click event of the Submit button in Angular? I want the style to change when the button is clicked. HTML <div class="mainbody" [ngClass]="getStyle"> <button (click)=&quo ...

Acquiring an element through ViewChild() within Angular

I am in need of a table element that is located within a modal. Below is the HTML code for the modal and my attempt to access the data table, which is utilizing primeng. <ng-template #industryModal> <div class="modal-body"> <h4>{{&a ...

What is the process for exporting a plugin from dayjs() in JavaScript?

Currently, I have incorporated the plugin isToday() to enhance the capabilities of dayjs(). Nevertheless, I am uncertain about how to export isToday() in order to utilize it across other files. import isToday from "dayjs/plugin/isToday"; expor ...

Issue with Angular 6: Animation with LESS is failing to work post deployment

Hello everyone, I'm facing an issue with my LESS animation. When I deploy my app on the server after using ng build --prod, the CSS animations are not visible in browsers. They work perfectly fine on localhost but fail to work after deployment and I c ...

Failure in SystemJS during ahead-of-time compilation due to missing NgZone provider

Previously, I have successfully used Angular's ahead-of-time compilation. However, after adding routing and lazy loading to my app, I am facing difficulties in making it work again. Upon updating my code to the latest 2.0 release, it functions well w ...

Open the JSON file and showcase its contents using Angular

I am attempting to read a JSON file and populate a table with the values. I've experimented with this.http.get('./data/file.json') .map(response => response.json()) .subscribe(result => this.results =result, function(error) ...

The error message ``TypeError [ERR_UNKNOWN_FILE_EXTENSION]:`` indicates a

I am encountering an error while trying to run the command ./bitgo-express --port 3080 --env test --bind localhost: (node:367854) ExperimentalWarning: The ESM module loader is experimental. internal/process/esm_loader.js:90 internalBinding('errors ...

Delete row from dx-pivot-grid

In my current project, I am utilizing Angular and Typescript along with the DevExtreme library. I have encountered a challenge while trying to remove specific rows from the PivotGrid in DevExtreme. According to the documentation and forum discussions I fo ...

How can I create a bold, three-dimensional line using Three JS LineBasicMaterial?

Creating a new basic line material using THREE.js often does not yield the desired result. I am looking for an easy way to draw 3D lines with different colors and widths, suitable for a rotating scene. I have looked into various methods like Windows & An ...

Unlock the full potential of working with TaskEither by utilizing its powerful functionality in wrapping an Option with

After exploring various examples of using TaskEither for tasks like making HTTP requests or reading files, I am now attempting to simulate the process of retrieving an item from a database by its ID. The possible outcomes of this operation could be: The i ...

Employing a boolean constant to verify if a parameter has been specified

Struggling with TypeScript version 2.8.3, I'm confused as to why the code below is failing to recognize that params is defined inside the if block. const testFunction = (params?: string) => { const paramIsDefined = typeof params !== 'undefi ...