Is it possible to establish two properties in Typescript that are mutually exclusive?

Defining Optional Properties in Typescript Interface

In Typescript, I am looking to define an interface with two optional properties. It is important that only one of these two properties is present within the instance object of this interface.

Attempted Solution

interface ISidebarCommon {
  /**
   * The label to be used in sidebar
   */
  label: string;
  /**
   * Icon class in string format for the icon of the sidebar image
   */
  icon: string;
}

interface IRoutableSidebarItem extends ISidebarCommon {
  /**
   * Role number to determine which route to redirect the user to
   * This property is mutually exclusive with children
   */
  role: number;
}

interface ITreeSidebarItem<SI> extends ISidebarCommon {
  /**
   * An array of children sidebar items.
   * This property is mutually exclusive with role
   */
  children: SI[];
}

interface ISidebar {
  [index: number]: IRoutableSidebarItem | ITreeSidebarItem<IRoutableSidebarItem
    | ITreeSidebarItem<IRoutableSidebarItem>
  >;
}

Issue with Current Approach

Although my current solution ensures either role or children must be present, it does not enforce them as mutually exclusive. This means both properties can coexist within the instance object without triggering any errors during interface checks.

Illustrative Example

Below is a demonstration of an instance of the ISidebar interface where objects contain both role and children, yet no linting errors are displayed:

const sidebarBroken: ISidebar = [
  {
    label: 'l1',
    icon: 'c1',
    role: 5,
    children: [
      {
        label: 'l2',
        icon: 'c2',
        role: 6,
        children: [
          {
            label: 'l3',
            icon: 'c3',
            role: 7,
          },
        ],
      },
    ],
  },
];

Answer №1

I managed to find a workaround for this issue utilizing the never type in typescript and optional properties.

The solution involved updating my interface declarations so that an instance of IRoutableSidebarItem will never contain a children property, and an instance of ITreeSidebarItem will never have a role property.

An example of the updated code using the aforementioned workaround:

interface ISidebarCommon {
  /**
   * The label used in the sidebar
   */
  label: string;
  /**
   * String format icon class for the sidebar image icon
   */
  icon: string;
}

interface IRoutableSidebarItem extends ISidebarCommon {
  /**
   * Numeric role determining user redirection route
   * This property cannot coexist with children
   */
  role: number;
  children?: never;
}

interface ITreeSidebarItem<SI> extends ISidebarCommon {
  /**
   * Array of child sidebar items
   * This property cannot coexist with role
   */
  children: SI[];
  role?: never;
}

interface ISidebar {
  [index: number]: IRoutableSidebarItem | ITreeSidebarItem<IRoutableSidebarItem
    | ITreeSidebarItem<IRoutableSidebarItem>
  >;
}

/*
  Linting error thrown
*/
const sidebarBroken: ISidebar = [
  {
    label: 'l1',
    icon: 'c1',
    role: 5,
    children: [
      {
        label: 'l2',
        icon: 'c2',
        role: 6,
        children: [
          {
            label: 'l3',
            icon: 'c3',
            role: 7,
          },
        ],
      },
    ],
  },
];

/*
  No linting error generated
*/
const sidebarWorking: ISidebar = [
  {
    label: 'l1',
    icon: 'c1',
    children: [
      {
        label: 'l2',
        icon: 'c2',
        children: [
          {
            label: 'l3',
            icon: 'c3',
            role: 7,
          },
        ],
      },
    ],
  },
  {
    label: 'l1',
    icon: 'c1',
    role: 12,
  },
];

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

Invoking vscode Extension to retrieve data from webview

One task I'm currently working on involves returning a list from the extension to be displayed in the input box of my webview page. The idea is for a JavaScript event within the webview to trigger the extension, receive the list object, and then rend ...

What is the process for bringing a graphql file into typescript?

I'm having trouble importing a graphql file and encountering an error import typeDefs from "./schema/schema.graphql"; 10 import typeDefs from "./schema/schema.graphql"; ~~~~~~~~~~~~~~~~~~~~~~~~~ at cre ...

Pay attention to modifications in a property within an object in the React environment

Below is a React Context Provider containing a simple Counter class with methods stored in the context. import { createContext, useContext } from "react"; class Counter { public count: number = 0; getCount = () => { return this.count ...

Failure to invoke Jest Spy

Currently, I am attempting to conduct a test utilizing the logging package called winston. My objective is to monitor the createlogger function and verify that it is being invoked with the correct argument. Logger.test.ts import { describe, expect, it, je ...

How can we prevent new chips (primeng) from being added, but still allow them to be deleted in Angular 2?

Hey there! I'm currently exploring how to make the chips input non-editable. I am fetching data objects from one component and using the keys of those objects as labels for the chips. Check out my HTML code below: <div class="inputDiv" *ngFor="le ...

Ways to remove elements from array of objects that match items in separate array of strings using javascript

In my JavaScript code, I am looking to filter an array of objects based on an array of strings. Here is the input array of objects: const input = [ { id: 1, name: 'first', type: 'first_type', }, { ...

In the d.ts file, Typescript simply creates the line "export {};"

After executing the tsc command to compile my project into the dist directory, I noticed that typescript is generating an incorrect or empty d.ts file. Here is a snippet of my tsconfig.json: { "compilerOptions": { "module": " ...

The Order ID field in the Serenity-Platform's Order Details tab is not registering orders

I've been working on replicating the functionality of Orders-Order detail in my own project. https://i.stack.imgur.com/Bt47B.png My custom module is called Contract and Contract Line item, which I'm using to achieve this. https://i.stack.imgur ...

Angular Error: Cannot call function panDelta on this.panZoomAPI

Check out my small demonstration using a stackblitz, I'm having an issue. In the setup, there's a master component with pan-zoom functionality containing a parent component with children content. The library in use is ngx-panzoom. The default c ...

Generate an Observable<boolean> from a service function once two subscriptions have successfully completed

I am working on setting up a simple method to compare the current username with a profile's username in an Angular service. It is necessary for the profile username and user's username to be resolved before they can be compared. How can I create ...

Exploring the Powers of Typescript Interfaces within Interfaces

Can you assist me with implementing an interface wrapped within a second interface? Here is the code snippet for the reducer: import { createSlice } from '@reduxjs/toolkit'; export interface IStep { id: number; label: string; value: string ...

Did the IBM MobileFirst client miss the call to handleFailure?

I am currently utilizing the IBM MFP Web SDK along with the provided code snippet to send challenges and manage responses from the IBM MobileFirst server. Everything functions properly when the server is up and running. However, I have encountered an iss ...

What steps should I take to resolve the missing properties error stating '`ReactElement<any, any>` is lacking `isOpen` and `toggle` properties that are required by type `SidebarInterface`?

I've encountered an issue with two React components that appear to be configured similarly. The first component is a Navbar: type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyof T, Keys>> & { ...

Altering the background color of an individual mat-card component in an Angular application

Here is the representation of my mat-card list in my Angular application: <div [ngClass]="verticalResultsBarStyle"> <div class="innerDiv"> <mat-card [ngClass]="barStyle" *ngFor="let card of obs | async | paginate: { id: 'paginato ...

"Learn the process of integrating Javascript files from the Angular assets folder into a specific Angular component or module (such as Angular 2, 4,

I have custom1.js, custom2.js, and custom3.js JavaScript files that I need to load into Angular components component1, component2, and component3 respectively. Instead of adding these files to the index.html globally, I want to load them specifically for e ...

The declaration file for the module 'styled-components/native' appears to be missing

When incorporating styled-components into your React Native application, a separate sub-directory is created for the native components: import styled from 'styled-components/native`; export const Container = styled.View` ... `; If you attempt to i ...

Accessing properties for objects with map-like characteristics

Many interfaces allow for arbitrary data, as shown below: interface Something { name: string; data: { [key: string]: any }; } The problem arises when trying to access or set values on objects with arbitrary keys in Typescript. let a: Something = { ...

The border of the Material UI Toggle Button is not appearing

There seems to be an issue with the left border not appearing in the toggle bar below that I created using MuiToggleButton. Any idea what could be causing this? Thank you in advance. view image here view image here Just a note: it works correctly in the ...

Step-by-step guide on converting a file upload to a JavaScript object in Angular 11

I need to parse a JSON file into an object before sending it for validation in an API call. The current code is not working and the API requires an object. <label class="form-label required" for="file">File</label> <label ...

Tips for extracting the y-coordinate from a touch event using d3

I am utilizing d3.js to display data in my Ionic app. I have a touch event that allows me to move a line and retrieve the coordinates where it intersects with my chart. While I can easily obtain the x-coordinate representing the date, I am struggling to ge ...