How to conditionally make a property optional in Typescript depending on the value of another property

I'm a newcomer to Typescript and I've encountered a scenario that has been difficult for me to find a solution for. Any suggestions would be greatly appreciated.

My goal is to have the property options be optional when the type is either SHORT_TEXT or LONG_TEXT.

export interface Question {
  [keyName: string]: any;
  type: QuestionType;
  id: string;
  label: string;
  helpText?: string;
  placeholder?: string;
  value?: string;
  options?: Option[];
  validations?: any;
  required?: boolean;
  priority?: number;
  intent?: Intent[];
}

export type QuestionType =
  | "DROPDOWN"
  | "CHECKBOX"
  | "RADIO"
  | "SHORT_TEXT"
  | "LONG_TEXT";

export interface Option {
  id: string;
  label: string;
  value?: string;
}

Answer №1

It appears that what you're seeking is known as a "Discriminated Union". Simply put, this concept allows the type in a union to be narrowed based on the value of a specific property. In your scenario, you are looking for one (or more) types where the options property is required, and another type where it is optional.

export type QuestionType =
  | "DROPDOWN"
  | "CHECKBOX"
  | "RADIO"
  | "SHORT_TEXT"
  | "LONG_TEXT";

export interface Option {
  id: string;
  label: string;
  value?: string;
}

export interface Intent {};

export interface BaseQuestion {
  [keyName: string]: any;
  type: QuestionType;
  id: string;
  label: string;
  helpText?: string;
  placeholder?: string;
  value?: string;
  validations?: any;
  required?: boolean;
  priority?: number;
  intent?: Intent[];
}

export interface TextQuestion extends BaseQuestion {
    type: "SHORT_TEXT" | "LONG_TEXT";
    options?: Option[];
}

export interface OptionQuestion extends BaseQuestion {
    type: "DROPDOWN" | "CHECKBOX" | "RADIO";
    options: Option[];
}

export type Question = TextQuestion | OptionQuestion;


const q: Question = {
    type: "SHORT_TEXT",
    id: "id_foo",
    label: "Test Question",
};

You can experiment with this further in the Typescript playground: you'll see that the validator accepts the q object without an options property. However, changing the type property on q to something other than "SHORT_TEXT" or "LONG_TEXT" will prompt an error from the validator.

If you examine q in the interactive playground, you'll notice that it is identified as a TextQuestion due to type narrowing. While this prevents altering the q.type property later, keep in mind that this guard only applies within blocks where the type of the union has been narrowed already. For instance, if you iterate over a list of questions and reassign the type:

(questions as Question[]).forEach(q => {
  q.type = "DROPDOWN";
});

In this scenario, Typescript does not narrow the type of each object and won't prevent reassigning the type.

I hope this information is helpful! ✌️

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 are the steps to set up a dictionary with predetermined values?

My task is to create a pre-defined dictionary where the key represents a city and the value is an array of zones in that city. Here is my attempt: export const cityToZone: { [city: string]: Array<string> } = [ {city:'New York', [&apos ...

Why aren't special methods/mixins for Sequelize associations being generated?

According to the documentation available at https://sequelize.org/docs/v6/core-concepts/assocs/#special-methodsmixins-added-to-instances: When establishing an association between two models, special methods are introduced that enable instances of those mo ...

Guide to making a Material Design Radial effect animation

I am looking to create a unique toolbar effect by following the material design radial reaction choreography guideline. https://i.stack.imgur.com/6oB8r.gif I want to achieve this using an angular 2 transition, but I need some guidance on how to implement ...

best method for implementing a TypeScript object

I've been searching high and low, but I can't seem to find the exact answer I need. Please inform me if my question has already been asked. My goal is to construct an object in TypeScript that includes attributes of custom types. The issue I&ap ...

Unspecified data stored within an object

I am looking to populate a page with data from the server and have the ability to update the information. To achieve this, I am using formbuilder to fetch data from the server as the default value. Here's how I am implementing it: createForm(){ ...

Angular 2+ enables the creation of dynamic folders and allows for uploading multiple files to the server in a seamless

Can an Angular application be developed similar to FileZilla or Core FTP for uploading files and folders to the server via FTP? I need a way to upload files and folders through the admin panel instead of relying on external applications like FileZilla. ...

Check if a value is present in the array with *ngIf

I'm curious about how to use the ngIf directive in a specific scenario. In my Angular application, I have dynamically generated angular material toggles, each with a unique id. I'm familiar with using ngIf to conditionally display elements on the ...

How can I configure a mocked dependency in Jest to return a specific value in a function?

I am currently working on simulating a mongoose model to facilitate unit testing for an express controller. To keep things simple, I've removed all unnecessary code and provided below the specific scenario I'm dealing with. Here's the snippe ...

Exploring the visitor design pattern with numerical enumerated types

I am exploring the use of the visitor pattern to ensure comprehensive handling when adding a new enum value. Here is an example of an enum: export enum ActionItemTypeEnum { AccountManager = 0, Affiliate = 4, } Currently, I have implemented the fol ...

Limit the category to a specific subset of strings

Looking for a way to implement literal type restrictions without using type aliases: const foo = (a: 'string', b: 'string') => { } foo("123", "abc") // should fail foo("123" as 'string', "abc" as 'string') I pr ...

Having trouble accessing store state in Angular with NGXS

In the parent component, I am dispatching an action and hoping to receive the dispatched array in the child component. To achieve this, I have implemented the following code: export class ListComponent implements OnInit { @Select(ProductState.getProductD ...

Unable to send a function as props to a child component in TypeScript

Within my application, I have a parent component that holds a state and a setState function. This state and function are then passed down to a child component in order to retrieve values (such as input field data). When the function is triggered in the chi ...

Why does the onBlur event function in Chrome but fails to work in Safari?

I've encountered a problem with the onBlur event in react-typescript. To replicate the issue, I clicked the upButton repeatedly to increase the number of nights to 9 or more, which is the maximum allowed. Upon further clicking the upButton, an error m ...

Exploring Angular 12: utilizing rxjs observables with chained subscriptions for enhanced functionality and robust error handling

Can someone help me with creating a function that I can subscribe to, returning: an observable containing the result if all operations were successful an observable with an empty string in all other scenarios foo(): void { this.uploadImage.subscr ...

I encountered an issue with my TypeScript function in Angular, as it is unable to process multiple uploaded files

I'm having trouble with my TypeScript function in Angular that is unable to read multiple uploaded files. fileUpload(event: Event) { const self = this; this.imageUploadInp = event.target as HTMLInputElement; this.imageUploadInp.addEventLis ...

Unable to access CommonModule in Angular 10 component loaded dynamically

I'm currently working on a project using Angular. One of my methods involves dynamically creating a component, but I'm encountering difficulties when trying to use directives like ngClass and ngIf from the CommonModule within this component. Her ...

Adjust the alignment of divs in Angular

In developing a dashboard, I have successfully displayed key value pairs in a component by retrieving them from an environment.ts file. Now, I am looking to rearrange the positioning of the individual testcard components to align with a specific layout sho ...

Combining namespaces in Typescript declaration files

Currently, I am attempting to combine namespaces from d.ts files. For example, when I attempt to merge namespaces in a single file, everything works as expected. declare namespace tst { export interface info { info1: number; } var a: ...

What is the difference between TypeScript's import/as and import/require syntax?

In my coding project involving TypeScript and Express/Node.js, I've come across different import syntax options. The TypeScript Handbook suggests using import express = require('express');, while the typescript.d.ts file shows import * as ex ...

TypeScript's type 'T' has the potential to be instantiated with any type, even if it is not directly related to 'number'

Let's say we have a function that takes a value (for example, number) and a callback function that maps that value to another value. The function simply applies the provided callback: function mapNumber<T>(value: number, mapfn: (value: number) = ...