Customizable features depending on the generic type

Is there a way to make a property optional based on the generic type in TypeScript? I've attempted the following:

interface Option<T extends 'text' | 'audio' | 'video'> {
    id: string;
    type: T;
    text: T extends 'text' ? string : undefined;
    media: T extends 'audio' | 'video' ? T : undefined;
}

const option: Option<'text'> = { text: "test", type: "text", id: "opt1" };

Basically, the goal is to have the text property only for Option<'text'> and the media property only for Option<'audio' | 'video'>.

However, when compiling, an error is thrown by the TypeScript compiler:

Property 'media' is missing in type '{ text: string; type: "text"; id: string; }' 
but required in type 'Option<"text">'.ts(2741)

What would be a good workaround for this situation?

Answer №1

When it comes to having the optionality of a property depend on a generic type parameter within an interface, there is a workaround using a type alias and intersections:

type Option<T extends 'text' | 'audio' | 'video'> = {
    id: string;
    type: T;
} 
& (T extends 'text' ? { text: string } : {})
& (T extends 'audio' | 'video' ? { media: T }: {});


const option: Option<'text'> = { text: "test", type: "text", id: "opt1" };

Check this out

If you prefer a more streamlined approach, consider utilizing a discriminated union:

type Option = 
| { id: string; type: 'text'; text: string }
| { id: string; type: 'audio' | 'video'; media: 'audio' | 'video' };


const option: Extract<Option, {type: 'text' }> = { text: "test", type: "text", id: "opt1" };

function withOption(o: Option) {
    switch(o.type) {
        case 'text': console.log(o.text); break;
        default: console.log(o.media); break;
    }
}

Try it out here

Answer №2

One way to achieve this is by using a union:

type Option<T extends 'text' | 'audio' | 'video'> =
    {
        id: string;
        type: T;
    }
    &
    (
        T extends 'text'
        ? {text: string}
        : {media: T}
    );

const option: Option<'text'> = { text: "test", type: "text", id: "opt1" };

Link to Playground

Answer №3

Creating a generic type with string values like the one you mentioned,

interface Option<T extends 'text' | 'audio' | 'video'>
, is not feasible.

However, if you wish to achieve something similar, you can follow these steps:

  • option.ts
interface Option {
    id: string;
    type: 'text' | 'audio' | 'video' | undefined;
}
  • media-option.ts
interface MediaOption extends Option {
    media: string;
}
  • text-option.ts
interface TextOption extends Option {
    text: string;
}

When utilizing these types, you will need to cast to a specific type depending on whether it's a media or text option, based on its type.

let option: Option = {id:'1',type: 'audio'}

if(option.type === 'audio'){
let media = option as MediaOption
}

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

Comparing Node.JS using Typescript versus Javascript to Ruby On Rails

My question can be simplified as follows: 1. Does Ruby perform as well as Node with MongoDB? 2. Should I use Typescript or JavaScript with Node? I come from a .NET background (ASP.NET MVC) and am now venturing into creating an Angular project with a Mongo ...

angular Struggling with @Input and data interpolation functionality within web components

I have built a collection of web components designed for various Angular projects. To make these components reusable, I am using @angular/elements to convert them into custom elements and then serving them via http-server. One of the components I develope ...

Definition of TypeScript array properties

Having some trouble creating a type for an array with properties. Can't seem to figure out how to add typings to it, wondering if it's even possible. // Scale of font weights const fontWeights: FontWeights = [300, 400, 500]; // Font weight alia ...

When attempting to publish an index.d.ts file using npm, the module is

We are currently in the process of developing an npm package that will serve as the foundation for most of our projects. However, we have encountered some issues that need to be addressed: The index.d.ts file of our base npm package is structured as shown ...

Send an API request using an Angular interceptor before making another call

Within my application, there are multiple forms that generate JSON objects with varying structures, including nested objects and arrays at different levels. These forms also support file uploads, storing URLs for downloading, names, and other information w ...

Upgrade to Typescript version 3.2 and exploring the Response body within lib.dom.d.ts

Just recently upgraded to Angular 7 and Typescript 3.2.2, and now one of my Jasmine spec tests is throwing an error. httpMock.expectOne({method: 'PUT'}).flush(new Response({status: 200})); The error message reads: Argument '{ status: ...

"Transferring a C# dictionary into a TypeScript Map: A step-by-step

What is the correct way to pass a C# dictionary into a TypeScript Map? [HttpGet("reportsUsage")] public IActionResult GetReportsUsage() { //var reportsUsage = _statService.GetReportsUsage(); IDictionary<int, int> te ...

A foundational NodeJS program in TypeScript featuring a structured client-utility-definition setup with adherence to stringent coding guidelines

What is the best way to set up a basic TypeScript framework for starting a program with strict settings, based on the following program structure? An initial "client" code containing the actual program logic A separate "utility" module for defining funct ...

Excluding certain source files in Typescript's tsconfig is a common practice to

My attempt to configure Typescript to exclude specific files during compilation is not working as expected. Despite setting exclusions in my tsconfig.json file, the code from one of the excluded files (subClassA.ts) is still being included in the compiled ...

What is the proper data structure for an array containing a generic interface?

How can I specify the correct type for routes array in order to prevent the error: TS2314: Generic type 'Route ' requires 1 type argument(s). View code in TypeScript playground interface Route<T> { path: string handler: () => T } ...

I desire to exclude the final attribute of the object and instead assign its value to the preceding property

I am dealing with an object structure like the one below: let a = { title: { value:"developer" } publishedOn:{ month:{ value:"jan" } year:{ value:"2000" } } and I need to transform it into the followin ...

Angular styling and form error issue

Hey there! I'm new to Angular and facing a major issue along with a minor styling problem. Let's start with the big one: <mat-form-field appearance="fill"> <mat-label>Password</mat-label> <input matInput ...

Modify the selection in one dropdown menu based on the selection in another dropdown menu using Angular 8

When I have two dropdowns, I aim to update the second dropdown with a matching JSON object based on the value selected in the first dropdown. JSON this.dropdownValues = { "mysql 8": { "flavor": [ "medium", ...

Issue with Vue router - Multiple calls to the "next" callback were detected within one navigation guard

I have incorporated Vue 3 with Vue router 4 and have implemented middleware functions that my routes must pass through. However, I am encountering an error in Vue that states: The "next" callback was invoked multiple times in a single navigation guard wh ...

Angular 11 throws an error stating that the argument of type 'null' cannot be assigned to a parameter of type 'HttpClient'

As I embark on my journey to becoming a developer, I encounter a problem when passing a null argument as shown below: todos.component.spec.ts import { TodosComponent } from './todos.component'; import { TodoService } from './todo.servic ...

The logic behind regular expressions

Currently working on building a web application with Angular 6 and I have a query: I'm in the process of developing a custom input component (specifically for text inputs) like so: @Component({ selector: 'input-text', templateUrl: &apos ...

Change the class properties to UpperCamelCase

I am facing a challenge with attributes in my TypeScript class that are written in camelCase format. The instance of this class needs to be used in an HTTP request body for a web service that has its backend written in C#. However, the backend is trying to ...

Retrieve information from a service in Angular to populate a form

I am working on creating a form in Angular, and I need some placeholder data to fill in the form fields. This data is fetched from a service as JSON with three specific fields. Due to my limited understanding of TypeScript, I may be making a basic mistake ...

Display a React component according to the user's input

Within the first (parent) div, there is an underlined message stating: "This JSX tag's 'children' prop expects a single child of type 'ReactNode', but multiple children were provided.ts(2746)". import A from './components/A&ap ...

Deactivating PrimeNG checkbox

I am currently facing an issue with disabling a PrimeNG checkbox under certain conditions by setting the disabled property to true. However, whenever I click on the disabled checkbox, it refreshes the page and redirects me to the rootpage /#. To troublesh ...