Developing a user interface that filters out a specific key while allowing all other variable keys to be of the identical type

As I dive deeper into the TypeScript type system, I find myself grappling with an interface design query. Can anyone lend a hand?

My goal is to craft an interface in TypeScript where certain object keys are of a generic type and all other keys should be of a consistent type. Despite my attempts with conditional types and union types containing 'never', I haven't been able to crack the code on this.

type Command = () => void;
type ViewState = {[key: string]: any};

export interface ViewModel<T extends ViewState> {
  viewState: T;
  // The following line is what does not work
  [key: Omit<string, 'viewState']: Command;
}

// Here's the desired outcome,
interface LabelState {
  label: string;
}

interface LabelModel<LabelState> {
  viewState: LabelState;
  updateLabel: Command;
}

interface MenuState {
  menuItems: string[];
}

interface MenuModel<MenuState> {
  viewState: MenuState;
  openMenu: Command;
  closeMenu: Command;
};

I realize this may not be essential, but it would be satisfying to have all methods defined as a Command. It seems like it should be doable. Is there someone out there who can confirm whether or not this is achievable? And if so, what am I overlooking?

Answer №1

If you're looking for a different approach, consider the following strategy:

type ViewModel<T> = {
  [K in "viewState" | keyof T]: K extends "viewState" ? ViewState : Command
};

The concept of the ViewModel type being generic lies not in the state's type, but rather in how ViewModel itself is extended. To illustrate this further, let's take a look at some examples:

interface LabelModel<S> extends ViewModel<LabelModel<S>> {
  viewState: S;
  updateLabel: Command;
}

interface MenuModel<S> extends ViewModel<MenuModel<S>> {
  viewState: S;
  openMenu: Command;
  closeMenu: Command;
}

These are similar to your existing LabelModel and MenuModel types, but by using extends ViewModel<...>, it enforces the restriction that you mentioned. This kind of construction, like F-bounded polymorphism, allows an interface to reference its own type. TypeScript offers a feature known as "polymorphic this" for achieving this without an extra type parameter, although it may not work within mapped types as shown here.

In essence, ViewModel<T> transforms a type T into a new type where all properties except those with keys "viewState" have a value type of Command, along with a property "viewState" of type ViewState. By declaring

interface X extends ViewModel<X>
, we specify that X must be compatible with an object with only "viewState" as a non-Command property.

To see what occurs upon violating this constraint:

interface BadModel extends ViewModel<BadModel> { // error!
  viewState: ViewState,
  notACommand: string
} // Type 'string' is not assignable to type 'Command'.

As demonstrated, the constraint is indeed enforced. I trust this information satisfies your requirements or offers fresh insights. Best of luck!

Link to code

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

Learn the process of typing a property that will be displayed as a dynamic HTML element component

Looking for a way to render an HTML element dynamically based on a prop in a React component? interface ButtonProps { children: ReactNode; className?: string; as?: string; <--- ? [key: string]: unknown; } const Button = forwardRef({ children, ...

Navigating dropdown list items using the keyboard in TypeScript is a breeze

I've encountered a bug while working on my open-source project. The issue is with navigating the dropdown list items using the keyboard (arrow key/tab). I have implemented the keyboard-navigation logic, but I'm unsure of how to make it work corre ...

Eliminate duplicate dropdown options in Angular 2 using a filter function

Is there a way to filter reporting results in an Angular 2 dropdown list? I am currently attempting to do so within the *ngFor template but haven't had any success. I will also try using a custom pipe. The data is coming from a JSON array. Specificall ...

Testing the integration of socket.io with Angular through unit tests

Currently, I am in the process of unit testing an angular service within my application that is responsible for creating a socket.io client. The structure of my service can be seen below: export class SocketService { private name: string; private host ...

Double up on your calls to the subscribe function in Angular to ensure successful login

Within my angular 7 application, there is a sign in component that triggers the sign in function within the authentication service. This function initiates an HTTP post request and then subscribes to the response. My goal is to have both the auth service f ...

Troubleshooting ReactJs in Chrome during page load: encountering issues with undefined properties when utilizing the console

When debugging React on initial page load, I encounter a problem. Upon hovering over this, I can see the content, but using the console shows that this is undefined. Interestingly, this issue only arises when I refresh the page; clicking around does not tr ...

Tips for utilizing generated *.d.ts files

I have been utilizing a Visual Studio 2017 extension called TypeScript Definition Generator to automatically create TypeScript interfaces for my MVC-ViewModels. Despite trying various similar tools, they all seem to result in the same output (*.cs.d.ts-Fil ...

Step-by-step guide on developing an AngularJs provider using TypeScript

As I've developed a Directive that incorporates various Css classes, it would greatly enhance its flexibility if the Css classes could be configured at Application start within the config section. I believe utilizing a provider is the appropriate appr ...

enhancing the types of parameters in a function declaration without relying on generics

My goal is to improve developer experience (DX) by expanding the types for parameters in a function signature. I want the tooltip when hovering over the following function to provide more detailed information: type Props = { a: number; }; const func = ( ...

Error encountered in typescript when trying to implement the Material UI theme.palette.type

Just starting out with Material UI and TypeScript, any tips for a newcomer like me? P.S. I'm sorry if my question formatting isn't quite right, this is my first time on Stack Overflow. https://i.stack.imgur.com/CIOEl.jpg https://i.stack.imgur.co ...

Using Angular, create mat-checkbox components that are dynamically generated and bound using

In my Offer class, I have a property called "units" which is an array of objects. export class Offer { public propertyA: string; public propertyB: string; public units: Unit[]; } export class Unit { public code: string, public name: ...

disappearing of vue event on single file component HTML element

I'm currently working on an ElectronJs project with Electron Forge, using the Webpack + Typescript template project In addition to that, I've integrated Vue and vue-loader for webpack in order to utilize Single File Component (SFC) files: { ...

Using Angular 4 to transfer data from a dynamic modal to a component

Currently implementing material design, I have set up a dialogService for dynamically loading MdDialog. My goal is to create a search dialog with filters that, upon submission, directs the user to a search-results component route. However, I am struggling ...

Guide to accessing a nested and potentially optional object property with a default value and specifying its data type

Just a simple query here... my goal is to extract data.user.roles, but there's a possibility that data may be empty. In such cases, I want an empty array as the output. Additionally, I need to specify the type of user - which in this instance is any. ...

Tips for integrating an arrow function as a property in a functional programming approach using JavaScript or TypeScript

Suppose I were to create a constructor for a functional class with TypeA as an argument, and TypeB representing the type of the class itself. In such cases, I can implement it using: functionName(argument: TypeA): TypeB { this.property = argument; ...

Find the length of time in Typescript (measured in hours, minutes, and seconds)

Trying to calculate the duration between two dates in TypeScript (Angular): 2021-11-19 21:59:59 and 2021-11-19 22:00:18 let startDate: Date = new Date(start); let endDate: Date = new Date(end); if(end != null) { let duration = new Date(endDate.getT ...

What is the process for updating the internal TypeScript version in VS Code?

When using VS Code, I noticed it comes with its own TypeScript version: Is there a way to update this? The current version is 4.9.3. ...

Utilizing Vue class-style components for creating a recursive component

I'm currently working with a class-style component using the vue-property-decorator plugin. I want to create a recursive component that can use itself within its own structure. Here's a snippet of my code: <template> <ul> <li& ...

What is the best way to generate a switch statement based on an enum type that will automatically include a case for each enum member?

While Visual Studio Professional has this feature, I am unsure how to achieve it in VS Code. Take for instance the following Colors enum: enum Colors { Red, Blue, When writing a switch statement like this: function getColor(colors: Colors) { swi ...

Ensure that the hook component has completed updating before providing the value to the form

Lately, I've encountered an issue that's been bothering me. I'm trying to set up a simple panel for adding new articles or news to my app using Firebase. To achieve this, I created a custom hook to fetch the highest current article ID, which ...