Using an instance variable as a dynamic key in an object interface: A step-by-step guide

I have a class definition that looks like this:

declare class MyClass {
  key: string

  data: {
    [this.key]: string; // error: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type.
    otherProps: { [k: string]: number | undefined; }
  }
}

The goal is for the data object to have a specific type behavior where if the key matches the one passed into the constructor, it should map to a string. The desired functionality is demonstrated in the following code snippet:

class MyClass {
  myFunction () {
    const value1 = this.data[this.key] // value1 should be type string
    const value2 = this.data.otherProps.foo // value2 should be type number | undefined
  }
}

Unfortunately, this approach is not working and is resulting in an error, as indicated in the comment in the first code snippet. What can be done to resolve this issue?

Answer №1

To ensure that the compiler can accurately keep track of the specific type of key being passed in, we must make MyClass generic with that type. Therefore, instead of just MyClass, it should be MyClass<K extends string>.

class MyClass<K extends string> {

Next, we need to specify that the data property should have a key of type K with a corresponding value of type string. It may seem somewhat confusing at first, as the syntax for a computed property name, an index signature, and a mapped type all appear quite similar but have different rules regarding functionality.

Avoid writing { [this.key]: string } as it implies a computed property key, requiring a statically known literal/symbol type. Instead, since the key type K is generic, employ a mapped type using {[P in K]: string} or the equivalent

Record<K, string></code utilizing the <code>Record<K, V>
utility type.

If additional properties are needed for data, you'll need to intersect Record<K, string> with the remaining object type since mapped types do not permit the addition of other entries.

  data: Record<K, string> & { otherProps: { [k: string]: number | undefined } 

Furthermore, when creating the constructor, it's advisable not to solely rely on key of type

K</code, as this could lead to potential conflicts if someone attempts to pass in a value that matches an existing property. To prevent this, use <code>Exclude<K, "otherProps">
from the Exclude<T, U> utility type to ensure proper differentiation.

  constructor(key: Exclude<K, "otherProps">) {
    this.data = { [key]: "prop", otherProps: { a: 1, b: 2, c: 3 } } as typeof this.data;
  }
}

new MyClass("okay"); // okay
new MyClass("otherProps"); // error!

Let's now put our code to the test:

const c = new MyClass("hello"); // okay
console.log(c.data.hello.toUpperCase()); // PROP
console.log(Object.entries(c.data.otherProps).map(
  ([k, v]) => k + ":" + v?.toFixed(2)).join("; ")) // "a:1.00; b:2.00; c:3.00" 

Everything appears to be functioning correctly. The compiler recognizes that c.data includes a string-valued hello property, while also accepting otherProps as a dictionary of number values.

Link to Playground for code testing

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

Instructions on how to sign up for a worldwide technique that is known as

I have a file called globalvars.ts where I added a global method. How can I subscribe to this method in the ts page where it is being called? globalvars.ts; httpgetmethod(url:string) { var veri; var headers = new Headers(); headers.append(' ...

Angular2: Continuous User Authentication Guard

I am in the process of developing an application that requires strict authentication for all users. To enforce this, I have created a LoggedInGuard. However, I find myself having to include canActivate: [LoggedInGuard] in every route definition within my ...

The problem of parameter being NULL in a post request in a .Net Core 3.0 Angular application

This is my first venture into the world of .Net Core Angular projects, so I apologize if my question appears to be too basic. Despite researching similar issues, I am still unable to resolve my problem, which leads me to believe that I must be making a mis ...

Tips for simulating a configuration dependency using Proxyquire in TypeScript

Within my codebase, there exists a config.ts file that contains the following method: // Config interface is utilized to specify expected values export default function getConfig(): Config { return {amount: 50} } In a specific class located at ../src ...

Combining TypeScript into HTML resulted in an error: Uncaught ReferenceError clickbutton is not defined

Attempting to create a basic CRUD frontend without the use of any frameworks. I am encountering an issue when trying to include a TypeScript file (index.ts) in my index.html, as the functions called within it are showing as undefined. I understand that bro ...

Is the Angular Library tslib peer dependency necessary for library publication?

I have developed a library that does not directly import anything from tslib. Check out the library here Do we really need to maintain this peer dependency? If not, how can we remove it when generating the library build? I have also posted this question ...

Angular 10 introduces a new feature where BehaviorSubject can now hold and emit two

Currently, I am attempting to log in using Firebase. The login system is functioning correctly; however, I am facing a challenge in retrieving the error name from the authentication service and displaying it in my login component. SignIn(email: string, pas ...

Data that changes dynamically on a chart

When making a rest call to fetch data, I aim to populate the pieChartData with the obtained information. However, I am facing difficulties in achieving this task. Can someone guide me on how to accomplish this? import { Component, OnInit} from '@angu ...

I possess information stored within the array object below, and I aim to transform it into a different array object format

Here is the response I received from my API: let data = [ { date: '2021-04-27', formatted_date: 'Apr 27', location: [ { date: '2021-04-27', formatted_date: 'Apr 27', countr ...

Executing a component method from a service class in Angular

When trying to call a component method from a service class, an error is encountered: 'ERROR TypeError: Cannot read property 'test' of undefined'. Although similar issues have been researched, the explanations mostly focus on component- ...

Attempting to create a finalized build of Express using Typescript

Encountering the following error message when executing this command: npm run clear && tsc -P ./tsconfig.app.json && npm run post:build or tsc -p . Node version: v12.13.0 NPM: v6.14.2 Express: 4 Has anyone else faced a similar issue? > ...

Try skipping ahead to the following spec file even if the initial spec has not been completed yet

I have encountered an issue when trying to execute two spec files for Angular.js. The problem arises when I attempt to run both files consecutively - the browser initially opens for angular.js, then switches to angularCal.js without executing angular.js ...

Can we improve the coding of this as it seems inefficient and uses up too much room?

Do you think there is a more efficient way to write this code? It seems quite impractical and takes up a lot of space. Essentially, it's about the random chance of obtaining a rarity, like acquiring an Uncommon sword. if (Math.random() * 100 < 100 ...

Is there a way to both add a property and extend an interface or type simultaneously, without resorting to using ts-ignore or casting with "as"?

In my quest to enhance an HTMLElement, I am aiming to introduce a new property to it. type HTMLElementWeighted = HTMLElement & {weight : number} function convertElementToWeighted(element : HTMLElement, weight : number) : HTMLElementWeighted { elemen ...

Transporting a Typescript file to the customer using JavaScript

As a newcomer to typescript and in the process of creating a small application in visual studio 2013, I have noticed that when viewing the project in Chrome's developer tools, typescript files (*.ts) are being downloaded to the client. This could pote ...

Encountering an Unexpected Index Error with ngFor in Angular 4/5

I am struggling to create a list of inputs and I can't seem to get ngFor to work properly. <div *ngFor="let q of questions; let i = index" class="col-3"> <div class="group"> <input [(ngModel)]="q" [class.ng-not-empty]="q.length & ...

Create an array using modern ES6 imports syntax

I am currently in the process of transitioning Node javascript code to typescript, necessitating a shift from using require() to import. Below is the initial javascript: const stuff = [ require("./elsewhere/part1"), require("./elsew ...

typescript page objects in protractor are showing an undefined property

Hey there, I'm facing an issue while using POM in Protractor with TypeScript in Angular CLI. The error I'm encountering is "Cannot read property 'sendUsername' of undefined". Since I'm new to TypeScript, can someone guide me on how ...

TypeScript - ensuring strict typing for particular object keys

In my current project, I am using typescript and working on defining an interface that has the following structure: interface SelectProps<T> { options: T[]; labelKey: keyof T; valueKey: keyof T; } The type T in this interface can vary i ...

TypeScript struggles with resolving types in union types

Imagine you have a data structure that can either be an array of numbers or strings and you intend to iterate over this array. In TypeScript, there are various ways to handle this scenario that are all validated by the type checker. [1, 2].map(e => cons ...