How can we limit the parameter value depending on the keys of a generic parameter?

Below is a simplified example of what I am trying to achieve. The class B has a generic parameter "T" and I want to restrict the allowed values of some method parameters to only those that match the keys of the parameter "T".

In my simplistic understanding, the code below should do what I think I intend. However, I keep encountering errors on the specified lines.

My goal is to limit the values when calling something like the "foo" method to only those that correspond to the T parameter. For instance, b.foo("asdf") would be acceptable, but b.foo("qwerty") would not be (as "qwerty" is not in keyof A).

Any guidance on how to make progress with this would be greatly appreciated.

interface A {
    asdf: string;
}

type Names<X extends A> = X extends A ? keyof X : never;

class B<T extends A> {
    foo(N: Names<T>) {

    }

    baz(n: Names<T>) {
        this.foo("asdf"); // Argument of type '"asdf"' is not assignable to parameter of type 'Names<T>'.ts(2345)
        this.foo("qwerty"); // expect an error here, but not the other ones

        this.foo(n); // fine?
    }
}

interface C extends A {
    qwerty: string;
}

class D<T extends C> extends B<T> {
    bar() {
        this.foo("asdf"); // Argument of type '"asdf"' is not assignable to parameter of type 'Names<T>'.ts(2345)

        this.foo("qwerty"); // Argument of type '"qwerty"' is not assignable to parameter of type 'Names<T>'.ts(2345)
        this.baz("asdf"); // Argument of type '"asdf"' is not assignable to parameter of type 'Names<T>'.ts(2345)
        this.baz("qwerty"); // Argument of type '"qwerty"' is not assignable to parameter of type 'Names<T>'.ts(2345)
    }
}

Answer №1

Looking at the example provided, all that's required is to directly employ the utilization of the keyof operator instead of explicitly defining Names (although you could define

type Names<X extends A> = keyof X
):

interface A {
  asdf: string;
}


class B<T extends A> {
  foo(N: keyof T) {

  }

  baz(n: keyof T) {
    this.foo("asdf"); // acceptable
    this.foo("qwerty"); // error
    this.foo(n); // acceptable
  }
}

interface C extends A {
  qwerty: string;
}

class D<T extends C> extends B<T> {
  bar() {
    this.foo("asdf"); // acceptable
    this.foo("qwerty"); // acceptable
    this.baz("asdf"); // acceptable
    this.baz("qwerty"); // acceptable
    this.baz("blork"); // produces an error
  }
}

This particular code snippet compiles without any errors, achieving the intended result.


Alternatively, your definition of Names<T> closely resembles

type AllKeysOf<T> = T extends unknown ? keyof T : never;

(with the exception that Names's input is constrained to A). This represents a distributive conditional type, which divides the input into its individual union components, retrieves the keys for each component, and then combines them back into a union. So while keyof (A | B) corresponds to the intersection of keys from each union component, equivalent to (keyof A) & (keyof B), the type AllKeysOf<A | B> equates to the union (keyof A) | (keyof B). Depending on the specific use case, this distinction may be significant.

However, conditional types reliant on generic type parameters, like Names<T> within the bodies of the B and D classes, aren't actually assessed by the compiler. It temporarily defers their assessment, rendering them opaque types. Within the context of the baz() method in B<T>, for instance, the compiler lacks insight into the potential type represented by

Names<T></code, thus opting to reject everything as a precautionary measure. Therefore, although we might deduce that <code>"asdf"
can be assigned to
Names<T></code if <code>T extends A
, the compiler struggles with such deductions and issues warnings. Consequently, it's advisable to avoid utilizing generic conditional types unless absolutely necessary: when keyof T suffices, it should take precedence over Names<T>.

Access the Playground link to view the 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

Click the button to apply a custom pipe for filtering the Angular table

I have successfully implemented a custom pipe to filter a table based on two input fields within a form. However, I now want to add functionality where the filtering only occurs when a submit button is clicked, similar to a search function. While I have fo ...

Creating a new TypeScript file via the command line: A step-by-step guide!

When I want to create a new file named main.ts, I try to write the command but it keeps showing an error. "bash: code: command not found" https://i.stack.imgur.com/cpDy3.png ...

What is the best way to simulate the axios call within my express controller?

My axios call is already separated into a module and tested using mocked axios. However, I am facing a dilemma when it comes to testing the controller that imports this function. I understand that it needs to be mocked, but I am uncertain about the most ef ...

Comparison between modules and standalone components

It has come to my attention that there is a growing trend in Angular 17 to move away from using modules, opting instead for standalone components. This approach makes Angular more similar to Vuejs or React, where the concept of modules is not as prominent. ...

Using Rxjs to reset an observable with combineLatest

My scenario involves 4 different observables being combined using "combineLatest". I am looking for a way to reset the value of observable 2 if observables 1, 3, or 4 emit a value. Is this possible? Thank you! Mat-table sort change event (Sort class) Mat- ...

Why does React redirect me to the main page after refreshing the page, even though the user is authenticated in a private route?

In the process of developing a private route component that restricts unauthenticated users and redirects them to the homepage, we encountered an issue upon page refresh. The problem arises when the current user variable momentarily becomes null after a ...

How can I retrieve the value of an array using ngForm in Angular 2?

One concrete example is a component I created that looks like this. @Component({ selector: 'home', template: ` <form (ngSubmit)="onSubmit(f)" #f="ngForm"> <input type="text" ngControl="people"> ...

Discover the method for extracting a value from an interface and incorporating it into a separate function

I need help accessing the value of userType for a conditional statement. UserType: const RadioForm = (props) => { return ( <div> <label>Customer</label> <input type="radio&qu ...

I'm having trouble grasping the concept of 'globals' in TypeScript/NodeJS and distinguishing their differences. Can someone explain it to me?

As I review this code snippet: import { MongoMemoryServer } from "mongodb-memory-server"; import mongoose from "mongoose"; import request from "supertest"; import { app } from "../app"; declare global { function s ...

Enhance your TypeScript classes with decorators that can add new methods to your class

Can you explain property definition using TypeScript and decorators? Let's take a look at this class decorator: function Entity<TFunction extends Function>(target: TFunction): TFunction { Object.defineProperty(target.prototype, 'test& ...

The parameter type 'Contact' cannot be assigned to the argument type '{ [x: string]: any; }'

Issue: The argument of type '{ [x: string]: any; }' cannot be assigned to the 'Contact' parameter. The type '{ [x: string]: any; }' is missing properties such as id, contactType, and name ts(2345) const contact: { [x: stri ...

Loading an HTML file conditionally in an Angular 5 component

Consider a scenario in my project where I have a testDynamic component. @Component({ templateUrl: "./test-dynamic.html", // This file needs to be overriden styleUrls: ['./test-dynamic.css'] }) export class testDynamic { constructor() ...

Developing a custom data structure that identifies the keys of an object with a designated nested attribute

There is an intriguing scenario where dynamically defining types from a centralized JSON data store would prove extremely beneficial. Allow me to elaborate. The JSON file I possess contains a roster of various brands. // brands.json { "Airbus": { ...

Exploring Angular 8: Connecting elements to an array filled with objects

My goal is to achieve the following: https://i.sstatic.net/TQeKN.png In my form, I have 2 fields: description and price. When the plus button is clicked, additional input fields (Input 2 and Price 2) are generated dynamically. I want to bind these field ...

Update ngx-datatable when the user clicks on a different directive

My issue involves a Side Navigation with a docking feature and a data table in the Main View. When I undock the side navigation, the data table does not recalculate automatically, leaving empty space where the Side Navigation was. This requires me to manua ...

During the ng build process, an error is encountered stating, "Cannot read the property 'kind' of undefined."

Currently, I am working on a project that requires me to utilize ng build --prod in order to build a client. However, each time I run ng build --prod, I encounter the same persistent error message: ERROR in Cannot read property 'kind' of undefin ...

What is the process for overriding the module declaration for `*.svg` in Next.js?

The recent modification in Next.js (v11.0.x) has introduced new type definitions: For next-env.d.ts (regenerated at every build and not modifiable): /// <reference types="next" /> /// <reference types="next/types/global" /> ...

Tips for expanding/collapsing accordion tab with the click of a button

I need help with my accordion setup. Right now, I can expand/collapse an accordion tab by clicking anywhere on the tab. However, I would like to only be able to expand/collapse the tab when clicking on the "Click me" button and not on the accordion tab its ...

How can I assign two different colors based on the type in Typescript?

I am configuring a color property based on the nature of the display. colorStyle: { textAlign: "center", backgroundColor: "transparent", color: (theme.colors.BaseColor.Red as any).Red4, } The cu ...

Guide to declaring a dynamic type variable based on a constructor parameter

Consider the following class structure: class DataExtractor<T, K extends keyof T> { constructor(private data: T, private property: K) { } extractData(): T[K] { return this.data[this.property]; } } This approach may seem awkward ...