Dynamic interface created by property values

Presently, I have developed the following interface structure:

export interface Vehicle {
  id: number;
  type: 'car' | 'airplane';
  jet_engines?: number;
  wheels?: number;
}

My aim is to restrict cars from having the jet_engines property and airplanes from possessing the wheels property.

The desired usage scenario is as follows:

const func = (vehicle: Vehicle) => {
  if (vehicle.type === 'airplane') {
    // TypeScript should be informed about the existence of `jet_engines` within `vehicle`.
  }
}

I am looking for a solution that does not involve defining separate interfaces like this:

const func = (vehicle: Car | Airplane) => {

Is there a way to achieve this objective while preserving the structure of the Vehicle interface without converting it into a type?

Answer №1

Absolutely! It is possible:

define type Vehicle as {
  id: number
} and VehicleType

assign type VehicleType as Car or Airplane

designate Car as {
  type: 'car'
  wheels: number
}

specify Airplane as {
  type: 'airplane'
  jet_engines: number
}

initiate func to check vehicle of Vehicle => {
  if (vehicle.type === 'airplane') {
    // The TypeScript compiler should recognize that `jet_engines` is present in `vehicle`.
  }
}

Answer №2

To enhance your Vehicle object, consider the following updates:

export type Vehicle = 
| {
    id: number;
    type: 'car';
    wheels: number;
  }
| {
    id: number;
    type: 'airplane';
    jet_engines: number;
  }

This approach allows for specific handling based on the vehicle type:

if (vehicle.type === 'car') {
  // TS recognizes the vehicle has wheels
} else {
  // TS acknowledges the vehicle has jet engines
}

If there are shared attributes among different vehicle types, you can consolidate them like this:

type VehicleCommonFields = {
  id: number;
  // other common fields
}

type VehicleTypes = 
| {
    type: 'car';
    wheels: number;
  }
| {
    type: 'airplane';
    jet_engines: number;
  }

export type Vehicle = VehicleCommonFields & VehicleTypes

Answer №3

To streamline your vehicle management system, start by defining a VehicleType that encompasses all possible vehicles.

Next, establish a generic type that captures the shared attributes across all vehicle types.

Create specific interfaces for each vehicle subtype (Car and Airplane) and culminate with a union type that combines them.

For added structure, consider implementing a type guard function, reminiscent of the following example:

type VehicleType = 'car' | 'airplane';

export interface GenericVehicle <T extends VehicleType> {
  id: number;
  type: T;
}

interface Airplane extends GenericVehicle<'airplane'> {
  jet_engines: number
}

interface Car extends GenericVehicle<'car'> {
  wheels: number
}

type Vehicle = Airplane | Car

const isCar = (vehicle: Vehicle): vehicle is Car => {
  return vehicle.type === 'car';
}

function doSmth(vehicle: Vehicle) {
  if (isCar(vehicle)) {
    vehicle.wheels;
  } else {
    vehicle.jet_engines;
  }
}

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

Issue with dependency injection not functioning during ngOnInit in Angular 7 unit testing

I am currently working with Angular 7 and Angular CLI version 7.3.4. I have a component that is being unit tested which involves injecting 2 services. Below, you will find the essential code snippets including the stub causing errors, the spec file, and th ...

Using TypeScript with MongoDB aggregation

Recently, I've been working on a User model with the goal of achieving generic aggregation. Essentially, I want to be able to pass an array of objects to a function and have it execute smoothly. Let me provide you with a snippet of the getUser functi ...

Troubleshooting: Difficulty assigning a value to a nested object in Angular

I'm a beginner in Angular and TypeScript so please forgive me if this question seems silly. I am attempting to store the value of a returned object into a nested property within an object with a type of any. Unfortunately, it is not allowing me to do ...

Is there a way to customize the Color Palette in Material UI using Typescript?

As a newcomer to react and typescript, I am exploring ways to expand the color palette within a global theme. Within my themeContainer.tsx file, import { ThemeOptions } from '@material-ui/core/styles/createMuiTheme'; declare module '@mate ...

Angular 2 Update RC5: Router Provider Not Found

I'm encountering an issue with the latest Angular 2 RC5 router (router version is RC1). Below is the error log from the dev console: EXCEPTION: Error in /templates/app.component.html:2:0 ORIGINAL EXCEPTION: No provider for Router! This is how my ap ...

Adding a constant to a Vue component

Currently working on developing an application using Vue and TypeScript. I'm focused on refactoring some aspects, particularly moving hard-coded strings from a template to a separate constant. What has been implemented is as follows: export const va ...

Comparison: executing an immediately invoked function expression (IIFE) and a separate

During my recent refactoring of some legacy code, I stumbled upon the following example: // within another function const isTriggerValid = await (async () => { try { const triggers = await db.any(getTriggerBeforeBook, { param ...

What is the best way to access buffer data in TypeScript for Solana?

Is there a way to retrieve buffer data from TypeScript? I am attempting to use the public key to access all of my token lists, but I am only getting back an empty array of objects. import {Connection, Keypair} from "@solana/web3.js"; const Sola ...

Tips on preventing the need to redeclare property types in React constructor with Typescript

Imagine having a class structured like this: class PeopleByTag extends React.Component<RouteComponentProps<{ tag: string }> In order to perform actions in the constructor, such as fetching data, you typically need to define a props parameter. Ho ...

In Typescript, you can easily group a string into sections that consist of digits like 345-67, along with text containing a

I have a string that looks like this: "[111-11] text here with digits 111, [222-22-22]; 333-33 text here" and I am trying to parse it so that I can extract the code [111-11], [222-22-22], [333-33] along with their respective text descriptions. The challeng ...

Step-by-step guide on importing CSS into TypeScript

I have a global CSS file where I've defined all the colors. I attempted to import that value into TypeScript but it didn't work. This is my latest attempt: get sideWindowStyle(): any { switch (this.windowStyle) { case 'classicStyl ...

Dealing with nullable objects in Typescript: Best practices

Looking for a solution to have a function return an object or null. This is how I am currently addressing it: export interface MyObject { id: string } function test(id) : MyObject | null { if (!id) { return null; } return { ...

TypeScript failing to correctly deduce the interface from the property

Dealing with TypeScript, I constantly encounter the same "challenge" where I have a list of objects and each object has different properties based on its type. For instance: const widgets = [ {type: 'chart', chartType: 'line'}, {typ ...

Error in Angular compiler.js at line 2531: Multiple components found for this element, causing template parse errors

After browsing through various topics with a similar issue, I have not found any solutions that address my specific reasons. The problem lies with a standard primeng button component. <div [hidden]="isHidden||isProgramVisible" id="st ...

Shifting successive elements in an array alternates

I have been working on a pick list component and have come up with the following layout https://i.stack.imgur.com/TnHAp.gif Whenever I try to move consecutive items, it keeps toggling between those two Here's a snippet of my code: moveDown(){ ...

Is there cause for worry regarding the efficiency issues of utilizing Object.setPrototypeOf for subclassing Error?

My curiosity is piqued by the Object.setPrototypeOf(this, new.target.prototype) function and the cautionary note from MDN: Warning: Modifying an object's [[Prototype]] is currently a slow operation in all browsers due to how modern JavaScript engines ...

A static factory method within an abstract class

I am currently developing a class system using Typescript. The main structure consists of an abstract class called Component, which includes a static method called create(). This method is utilized on child classes to generate specific instances. abstract ...

Sharing methods between two components on the same page in Angular can greatly improve code efficiency

On a single page, I have two components sharing some methods and properties. How can I utilize a service to achieve this? See the sample code snippet below... @Component({ selector: 'app', template: '<h1>AppComponent1</h1>' ...

Angular does not support the functionality of having multiple material paginations within a single component

I am currently working on creating a component that contains two dataTables, each with a different dataSource. However, I am facing an issue where my Tables are not visible immediately after the component is loaded due to the *ngIf directive being used. Be ...

Exploring the Behavior of Subscribing to a Shared Service in Angular

Within my Angular project, I have implemented a service to share variables across different components. The code for this service is as follows: import { Injectable } from "@angular/core"; import { BehaviorSubject } from "rxjs"; @Injectable() @Injectable( ...