Navigating the use of a getter property key within a generic method signature

What I want to do is create a class with descendants that have a method signature that can adapt based on a compile-time fixed property, which can also be overridden.

Here's an example:

class Parent {
  public get config() {
    return {
      foo: 'lorem',
    };
  }

  public access<K extends keyof this['config']>(key: K): this['config'][K] {
    return this.config[key];
  }
}

class Child extends Parent {
  public override get config() {
    return {
      foo: 'lorem',
      bar: 'ipsum',
    };
  }
}

new Parent().access('foo');
new Child().access('bar');

This code snippet seems very close to being functional almost work, but TypeScript throws an error at return this.config[key]:

Type 'K' cannot be used to index type '{ foo: string; }'.(2536)

I'm a bit puzzled by this error because K should logically be a key from {foo: string;}.

Note: I understand that using generics and defining an interface could resolve this issue. However, I wanted to experiment and see if there was a way to simplify the code and only define the property (and its structure) within the class itself.

Answer №1

When dealing with K extends keyof this['config'], the type resolution is postponed. This results in K being of type { foo: "lorem" } in the Parent class, and of type

{ foo: "lorem", bar: "ipsum" }
in the Child class.

On the contrary, the type resolution of return this.config[key]; is immediate. This means that the type of this.config[key] will always be { foo: "lorem" }

In the Parent class, K is { foo: "lorem" } and this.config is of type { foo: "lorem" }, so everything aligns properly. However, in the Child class, K is

{ foo: "lorem", bar: "ipsum" }
while this.config remains as { foo: "lorem" }, resulting in mismatched types. This clarifies why the compiler throws the error:

Type 'K' cannot be used to index type '{ foo: string; }'.(2536)

For further details, check out the discussion initiated by Alec: issue #46954

The provided code snippet functions only if Child config extends the return type of Parent config, and requires an explicit type cast for matching types.

class Parent {
  public get config() {
    return {
      foo: 'lorem',
    };
  }

  public access<K extends keyof this["config"]>(this: Parent, key: K) {
    return this.config[key as keyof typeof this["config"]];
  }
}

class Child extends Parent {
  public get config() {
    return {
      foo: 'lorem',
      bar: 'ipsum',
    };
  }
}

new Parent().access('foo');
new Child().access('bar');

explore playground

Answer №2

As per the problem I brought up, it appears to be functioning correctly.

Here is RyanCavanaugh's response in its original form:


The issue arises when attempting property access on class fields without deferred type resolution, resulting in the type of `this.config` being plain `{ foo: string }` instead of `this["config"]`. This may cause complications in certain scenarios like the example provided. However, this behavior is essential for other cases to function properly, allowing constructs such as:

class Parent {
  public config = {
    foo: 'lorem',
  };
  public mut() {
    this.config.foo = "ergo";
  }

  public access<K extends keyof this['config']>(key: K): this['config'][K] {
    return this.config[key];
  }
}

This design choice ensures that the derived type can potentially require a more specific value for `foo`.

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

Unable to retrieve the third attribute of a Class using Angular2's toString method

Here is the code snippet I am working with: import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: ` <h1>Hello {{name}}</h1> <p><strong>Email:</strong> {{email}}< ...

What is the best way to access a state variable that has a union data type?

Is there a way to access a field of a state variable with a union type in TypeScript? Here is an example of a zustand store I have defined: import { create } from 'zustand' type HeightUnit = 'cm' | 'ft\' in"&ap ...

Arrange elements within an array according to a specific property and the desired sorting sequence

Looking for a way to sort an object array in Angular 16+ based on status. The desired status order is: [N-Op, Used, Unknown, Op] Here's the sample data: const stockList = [ { 'heading': 'SK', 'status': &a ...

In TypeScript, the choice between using `private readonly` within a class and

I have been contemplating the best method and potential impacts of referencing constants from outside a class within the same file. The issue arose when I was creating a basic class that would throw an error if an invalid parameter was passed: export cla ...

Creating a dynamic visual experience with Angular 2: How to implement multiple font colors

I have a text area which is connected to one string, with the default text color set to white. <textarea style="background-color: black;color:#fff;" [(ngModel)]="outputText"></textarea> The connected string contains multiple variables. retur ...

Craft fresh items within HTTP request mapping

I am currently working on a function that subscribes to a search api. Within the map function, my goal is to transform items into objects. I haven't encountered any errors in my code, but the response always turns out empty. Here's the snippet o ...

sort the array based on its data type

Recently diving into typescript... I have an array that is a union of typeA[] | typeB[] but I am looking to filter based on the object's type interface TypeA { attribute1: string attribute2: string } interface TypeB { attribute3: string attri ...

What is the procedure for utilizing custom .d.ts files in an Angular 15 project?

Currently, within my Angular 15 project, I am utilizing a package called bootstrap-italia. This particular package is dependent on the standard Bootstrap package and includes additional custom components and types. However, it should be noted that this pac ...

Struggling to establish object notation through parent-child relationships in Angular 2

Hi there, I am new to Angular and JavaScript. Currently, I am working on achieving a specific goal with some data. data = ['middlename.firstname.lastname','firstname.lastname']; During the process, I am looping through the .html usin ...

The best approach to typing a FunctionComponent in React with typescript

I'm diving into the world of React and TypeScript for the first time. Could someone verify if this is the correct way to type a FunctionComponent? type ModalProps = { children: ReactElement<any>; show: boolean; modalClosed(): void; }; co ...

Adding a badge to a div in Angular 6: What you need to know!

How can I add a badge to a specific div in Angular 6? I have dynamic div elements in my HTML. I want to increase the counter for a specific div only, rather than increasing it for all divs at once. For example, I have five divs with IDs div1, div2, div3, ...

Encountering an error in Cytoscape using Angular and Typescript: TS2305 - Module lacks default export

I am working on an Angular app and trying to integrate Cytoscape. I have installed Cystoscape and Types/cytoscape using npm, but I encountered an error when trying to import it into my project. To troubleshoot, I started a new test project before implement ...

Is VSCode disregarding tsconfig.json and compiling individual files, causing misleading error messages to appear?

After updating typescript, angular, and all the libraries in my project, I encountered a new issue that was not present before. Although I successfully ensured that my code builds without any errors or warnings from the command line, Visual Studio Code sta ...

Using Vue.js 3 and Bootstrap 5 to Create a Custom Reusable Modal Component for Programmatically Showing Content

Trying to develop a reusable Modal Component using Bootstrap 5, Vuejs 3, and composible API. I have managed to achieve partial functionality, Provided (Basic Bootstrap 5 modal with classes added based on the 'show' prop, and slots in the body a ...

No data found in the subrow of the datasource after the filter has been

I am working with a material table that has expandable rows. Inside these expanded rows, there is another table with the same columns as the main table. Additionally, I have implemented filters in a form so that when the filter values change, I can update ...

Troubleshooting Node.js import module errors

I have just discovered two files that I created using the TS language specification manual (on page 111). The first file, called geometry.ts, contains the following code: export interface Point { x: number; y: number }; export function point(x: number, y ...

What is the best way to connect a toArray function to an interface property?

Consider the following scenario: interface D { bar: string } interface B { C: Record<string, D> // ... additional properties here } const example: B = { C: { greeting: { bar: "hi" } } // ... additional properties here } Now I would like t ...

Exploring the Potential of Using ngIf-else Expressions in Angular 2

Here is a code snippet that I wrote: <tr *ngFor="let sample of data; let i = index" [attr.data-index]="i"> <ng-container *ngIf="sample.configuration_type == 1; then thenBlock; else elseBlock"></ng-container> <ng-template #t ...

Is time-based revalidation in NextJS factored into Vercel's build execution time?

Currently overseeing the staging environment of a substantial project comprising over 50 dynamic pages. These pages undergo time-based revalidation every 5 minutes on Vercel's complimentary tier. In addition, I am tasked with importing data for numer ...

Accessing the state from a child functional component and then adding it to an array of objects in the parent component

I'm facing a challenge with a parent component that needs to manage the data of its child components stored in an array of objects. My goal is to add new child components and maintain their information within the parent's state as an array of obj ...