Is it possible to make the 'keyof' property optional?

Illustrate an interface in the following way

interface Properties {
  apple?: string
  banana?: string
  cherry?: string
  date: string
}

Executing this code works as expected

type Sample1 = {
  [P in keyof Properties]: Properties[P]
}

const s1: Sample1 = {
  date: '1'
}

However, when I modify it like below, errors arise

type Keys = keyof Properties;

type Sample2 = {
  [P in Keys]: Properties[P]
}

// error Type '{ d: string; }' is missing the following properties from type 'Sample2': apple, banana, cherry(2739)

const s2: Sample2 = {
  date: '1'
}

Delving deeper

type CustomOmit<T, K extends keyof any> = { [P in Exclude<keyof T, K>]: T[P]; }

// Property 'apple' is missing in type '{ date: string; }' but required in type 'CustomOmit<Properties, "banana" | "cherry">'.
const o3: CustomOmit<Properties, 'banana' | 'cherry'> = {
  date: '1'
}

// No issues here!
const o4: Omit<Properties, 'banana' | 'cherry'> = {
  date: '1'
}

Why does CustomOmit trigger an error?

Answer №1

There's a significant distinction between

type Test1 = {  [P in keyof Props]: Props[P] }
and
type Test2 = { [P in Keys]: Props[P] }
.

Take a look at the first one: https://i.stack.imgur.com/J7dSW.png

In the first type, each property is optional.

Now, consider the second one: https://i.stack.imgur.com/TrYz3.png

In the second type, each property is required but may be undefined.

Refer to this flag for more information.

The reason TypeScript opted for this approach is due to the dynamic nature of JavaScript.

For instance:

const foo = { name: undefined }
foo.name // undefined
foo.surname // undefined

If you try to access a property that doesn't exist, JS returns undefined. Further details are available in the TypeScript docs.

Although this flag won't impact your example's behavior, it offers an official explanation for any errors encountered.

To resolve issues like these, you can simply make Test2 partial:

type Test2 = { [P in Keys]?: Props[P] } // <---- added question mark

Alternatively:

type Test2 = Partial<{ [P in Keys]: Props[P] }>

Why isn't keyof Props the same as Keys?

I overlooked this aspect while addressing the question.

It appears that in Test1, keyof Props directly iterates through the keys of the Props interface considering all modifiers, whereas in the iteration of Test2, TypeScript goes through a union of characters like a | b | c | d without knowledge about Props.

A similar scenario occurred when working with enums:


enum MyEnum {
    ONE,
    TWO
}

type Enumerate<Enum extends number | string> = keyof {
    [Prop in Enum]: Prop
}

type Result2 = Enumerate<MyEnum>

Enumerate should return the keys of MyEnum, which is a union of ONE | TWO, but it actually returns MyEnum

Answer №2

Here is where you can find the origin of Omit

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

So essentially, the difference lies in how keyof includes all keys as mandatory and creates an object, while Pick forms the object from the original T with optional declarations preserved.

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

I'm having trouble getting my Node.js and TypeScript project to run because I keep encountering the error message ".ts is recognized as an unknown file extension."

I encountered the following error message: TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" This issue arose after inserting "type": "module" into the package.json package.json { "name": &qu ...

Warning: Potential spacing issues when dynamically adjusting Material UI Grid using Typescript

When working with Typescript, I encountered an error related to spacing values: TS2322: Type 'number' is not assignable to type 'boolean | 7 | 2 | 10 | 1 | 3 | 4 | 5 | 6 | 8 | "auto" | 9 | 11 | 12'. No lint errors found Version: typesc ...

The impact of redefining TypeScript constructor parameter properties when inheriting classes

Exploring the inner workings of TypeScript from a more theoretical perspective. Referencing this particular discussion and drawing from personal experiences, it appears that there are two distinct methods for handling constructor parameter properties when ...

The variable X has been defined, but it's never actually utilized. Despite declaring it, I have not accessed its

I have encountered warnings in VSCode while using certain properties in my Angular component. The warnings state: '_id' is declared but its value is never read.ts(6133) (property) ItemEditComponent._id: number | undefined '_isModeEdit' ...

Scroll to the top on every Angular 5 route change

Currently, I am utilizing Angular 5 for my project. Within the dashboard interface, there are various sections with varying amounts of content. Some sections contain only a small amount of information, while others have large amounts of content. However, w ...

Error message "Angular build with --prod causes Uncaught TypeError: e is not a constructor when running ng serve"

I am encountering an issue while deploying my Angular application to production. The application functions correctly in development and had previously worked on production as well. To build the application, I am using: ng build --prod --base-href="/site/ ...

What sets apart the two methods of defining an event in a React component?

Can you explain the nuances between these two approaches to declaring events in a React component? Is it merely a matter of personal preference, or are there more subtle distinctions between them? interface PropsX { onClick: () => void; } const But ...

I'm encountering an issue with my Next.js development server at localhost:3001 where all routes are displaying a 404 not found page. What

Currently working on a Next.js dashboard app and encountering an issue where my localhost keeps redirecting me to the 404 page. This has happened before, but I can't recall how I resolved it. Here is the recurring problem: I attempted deleting the .n ...

Ways to implement es6 in TypeScript alongside react, webpack, and babel

Being new to front-end development, I have found that following a simple tutorial can quickly help me start tackling problems in this field. One issue I've encountered is with ES5, which lacks some of the tools that are important to me, like key-value ...

Is it possible to enhance the GamepadAPI's functionality?

I've been working on enhancing the built-in GamepadAPI by adding custom controller code. With TypeScript, I created a basic function to trigger a "gamepadconnected" event. // emulate gamepadconnected event function dispatchGamepadConnectedEv ...

Typescript versus ES5: A comparison of Node.js server-side applications written in different languages

Note: When I mention regular JavaScript, I am referring to the ES5 version of JS. As I lay down the groundwork for a new project, my chosen tech stack consists of Node.js for the back-end with Angular2 for the front-end/client-side, and Gulp as the build ...

What is the best way to search for a specific value in the Record definition?

In the documentation for Typescript, a type is defined to be used as keys into a Record<>. It seems like this is done to restrict and secure the keys that can be utilized. type CatName = "miffy" | "boris" | "mordred"; W ...

Guide on toggling mat-checkbox according to API feedback in Angular 6

Just starting out with angular 6 and I'm attempting to toggle the mat-checkbox based on the API response. However, I seem to be having trouble. All the checkboxes are showing as checked even when the API response is false. <div class="col-sm-12" ...

NG0303: Unable to establish a connection with 'ngbTooltip' as it is not recognized as a valid property of 'button'

ERROR: 'NG0303: Can't bind to 'ngbTooltip' since it isn't a known property of 'button'.' Encountering this issue in my Angular 12 project when running local tests, the ngbTooltip error is present in all .spec files. ...

Learning how to merge two observable streams in Angular2 by utilizing RxJS and the powerful .flatMap method

Within my Angular2 app, I have created an Observable in a Service called ContentService. Here is a simplified version of the code: @Injectable() export class ContentService { constructor(private http:Http, private apiService:ApiService) { th ...

What is the best way to display user data exclusively in Angular?

How can I ensure that users only have access to their own information rather than the entire database? I attempted to use an if-else statement to filter out the data I need, as shown in the example below, but it was unsuccessful. custom-history.component ...

Using Node.js and TypeScript to define custom data types has become a common practice among developers

I offer a variety of services, all yielding the same outcome: type result = { success: boolean data?: any } const serviceA = async (): Promise<result> => { ... } const serviceB = async (): Promise<result> => { ... } However, th ...

What is the process for transforming a JSON object into a TypeScript interface?

After receiving a JSON output from an api: { "id": 13, "name": "horst", "cars": [{ "brand": "VW", "maxSpeed": 120, "isWastingGazoline": true ...

Issue with IE11 compatibility in Angular2 (AngularQuickStart version 2.4.0) due to syntax error

Encountering an error in the browser console when attempting to run my Angular2 app on IE11. The error "Недопустимый знак" translates to unacceptable symbol. https://i.stack.imgur.com/0mHBC.png Here is a snippet of my index.html: <!DO ...

Which superclass does ReadonlyArray extend from?

Looking at this TypeScript code snippet: const arr: ReadonlyArray<string> = [ "123", "234" ]; arr.push("345"); An error is thrown by the TS compiler: Property 'push' does not exist on type 'ReadonlyArray<string>&apo ...