Xkonti
Xkonti

Reputation: 119

Conditional type depending on a value of function arguments

I'm trying to combine 2 separate Typescript methods into one with the same name getDevice. First function requires just a number to return a Device or null in case there's no device:

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

The second function takes 2 arguments. First one is either a number (like previous function) or a Device (result of previous function):

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 get the device #${deviceOrId}.`);
    return null;
  }
  return new deviceType(device);
}

A result of combining two methods would be 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;
  // In case deviceOrId is a number
  if (typeof deviceOrId === 'number') {
    device = this.devicesService.getDevice(deviceOrId);
    if (device == null) {
      console.warn(`Failed to get the device #${deviceOrId}.`);
      return null;
    }
    if (deviceType == null) return device;
  }

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

The problem is I can't wrap my head how to properly type the whole function:

Is it even possible in Typescript? If yes how? Maybe with some function overloading tricks?

Upvotes: 0

Views: 64

Answers (1)

Xkonti
Xkonti

Reputation: 119

There's an easy solution to the problem. You can overload functions in typescript (documentation). First you declare you function signatures. In case of this question it would be:

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

Then you write the function implementation that will handle the logic. That implementation function signature won't be considered as another overload. Full code:

// 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 get the device #${deviceOrId}.`);
      return null;
    }
    if (deviceType == null) return device;
  }
  return new deviceType(device);
}

Upvotes: 1

Related Questions