Utilizing Typescript to ensure property keys within a class are valid

Looking for advice to make a method more generic. Trying to pass Child class property keys as arguments to the Super.method and have Child[key] be of a Sub class.

class Parent {
  method<T extends keyof this>(keys: T[]){
  }
}

class Child extends Parent {
  a = new Sub;
  b = new Sub;
  c = new Sub;
  d = new Nope;
  e = new Nope;
}

child = new Child;
child.method(['a', 'b', 'c']);

All property keys are currently being displayed. However, unsure about filtering the classes next.

The assistant has displayed the keys as follows.

  • 'a'
  • 'b'
  • 'c'
  • 'd'
  • 'e'

Desire a change as shown below:

  • 'a'
  • 'b'
  • 'c'

Answer №1

Indeed, it is possible to make this happen automatically.

However, a specification for the Sub or Nope types was not provided, so I will define them. This distinction is crucial due to TypeScript's structural typing nature; it focuses on structure rather than names. If both Sub and Nope share the same properties, they will be considered the same type by the compiler, making it impossible to differentiate between them based on their class properties. Therefore, let's define them as follows:

// It's important that Sub and Nope are structurally distinct types
// for proper differentiation by the compiler
class Sub {
  sub!: string;
}
class Nope {
  nope!: string;
}

Now, Sub has a key named "sub", while Nope has a key named "nope", allowing the compiler to distinguish between them.

You can introduce a type alias called KeysMatching<T, V>, which retrieves keys from T where the property matches type V:

type KeysMatching<T, V> = {[K in keyof T]: T[K] extends V ? K : never}[keyof T];

This implementation utilizes mapped and conditional types. You can now properly type your method by replacing keyof this with KeysMatching<this, Sub>:

class Parent {
  method<T extends KeysMatching<this, Sub>>(keys: T[]){
  }
}

class Child extends Parent {
  a = new Sub;
  b = new Sub;
  c = new Sub;
  d = new Nope;
  e = new Nope;
}

Let's confirm its effectiveness:

const child = new Child;
child.method(['a', 'b', 'c']); // okay
child.method(['d','e']); // error!

It appears to be working correctly. You can test it further using this Playground link. Hopefully, this explanation helps you achieve your objectives. Best of luck!

Answer №2

To prevent d and e from being valid selections, you can either remove them from your class or set them as private. This will limit the auto-completion list to only display 'a', 'b', 'c', 'method' - with 'method' included due to inheritance from the base class.

If you want even more control, you could choose to restrict the names further, but this approach may require constant maintenance:

class Parent {
    protected baseMethod<T extends keyof this>(keys: T[]){
    }
}

class Child extends Parent {
    a = new Sub();
    b = new Sub();
    c = new Sub();
    d = new Nope();
    e = new Nope();

    method<T extends keyof this & 'a' | 'b' | 'c'>(keys: T[]) {
        this.baseMethod(keys);
    }
}

In the scenario above, trying to include d or e will trigger a warning message.

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

Maintain hook varieties during implementation of array deconstruction

I have been developing a straightforward hook to export animation helper and element reference. import { gsap } from 'gsap'; import { useRef } from 'react'; export function useTween<R extends gsap.TweenTarget>(vars: gsap.TweenVar ...

Typescript Error: TS2339: The property 'faillogout' is not found within the type '{ failed(): void; onSubmit(): void; }'

I encountered an issue with my Vue.js app using TypeScript. The error message I'm getting is: Property 'faillogout' does not exist on type '{ failed(): void; onSubmit(): void; }'. 101 | failed () { This snippet shows the s ...

What is causing the error message "Module '@reduxjs/toolkit' or its type declarations are not found" to appear?

Although I have a good understanding of React-Redux, I decided to delve into TypeScript for further practice. Following the recommended approach from the react-redux team, I created a new project using the TS template: "npx degit reduxjs/redux-templa ...

The Angular tutorial for the "Tour of Heroes" is experiencing issues with aligning the heroes' list properly

I am currently working on the Angular tour of heroes tutorial. However, I am facing an issue when trying to display the list of heroes as it appears like this: It is strange because even though the CSS/HTML/TS code from the tutorial seems correct, the lis ...

No solution was found for implementing Airbnb TypeScript in React.js (Next.js) using ESLint

screenshot I encountered an issue where I couldn't locate the Airbnb typescript option in React JS (Next JS) within ESLint. Prior to this, I had installed Storybook and mistakenly clicked "yes" when prompted to migrate ESLint to Storybook. ...

Unit tests in Jasmine disable dispatchers when NGXS store.reset is invoked

I am facing a challenge with an unusual behavior during the unit testing of my NGXS store using Jasmine. Specifically, I am encountering issues when trying to test the DeleteAlerts action : @Action(DeleteAlerts) deleteAlerts(ctx: StateContext<Alert ...

Can Schema and Model/Entity Files be Decoupled in TypeORM?

Having experience with PHP (Laravel/Eloquent, Symfony/Doctrine), I am accustomed to ORMs not defining schema but making schema attributes accessible. In my previous work, I never had to use a "Model" file to manage schema as it was always handled through m ...

Creating JPEG images with specified dimensions. How can you add W x H sizing to an image?

I have been searching for a Deno/TypeScript code snippet that can create basic images with dimensions embedded on them. I have provided an example of the code below, which generates images in JPEG format, base64, and dataURL. The code works by adding RGB ...

Executing an Observable function in Angular Typescript a single time

Within my Angular application, there exists a service responsible for retrieving data from a server. load.service.ts: load = new Observable(observer => { console.log('load function called'); // asynchronous tasks with time delay obser ...

Navigating nested data structures in reactive forms

When performing a POST request, we often create something similar to: const userData = this.userForm.value; Imagine you have the following template: <input type="text" id="userName" formControlName="userName"> <input type="email" id="userEmail" ...

When utilizing RxJS, the process of filtering Observable data may not function as expected if the filtering is carried out within a separate function rather than directly within the subscribe

While attempting to filter data from an external source using the RxJS filter on Observables, I encountered an issue where all records were returned instead of just the ones meeting the filtering criteria. This problem occurred when the code was within a l ...

Changing the default route in Angular 2 based on conditions

I'm currently developing an application where, upon entering the page, the default route for the user is the "Login" page. However, I want to implement a condition based on whether the user has a local storage variable (id) set. If this variable exist ...

Leverage a single attribute from a Typescript interface within another interface

Let's imagine we have a TypeScript Interface like this export interface IMyObj { id: string; type: 'AA' | 'AZ' | 'XY'; ... } Now, I require another interface that includes the same type field export interfa ...

Issue with reflect metadata in Next.js edge runtime causing functional problems

Currently, I am utilizing a package in my upcoming 13 app that incorporates reflect metadata. Unfortunately, during the next build process, an error occurs for which I haven't been able to find a solution. ../../eshop-sdk-js/node_modules/reflect-metad ...

Tips for implementing a delay in HTTP requests using RxJS 6.3.0

When I try to use delay with the HTTPClient object, it gives me the following error: Cannot invoke an expression whose type lacks a call signature. Type 'Number' has no compatible call signatures. TypeScript Concerns: import { delay } from & ...

Angular 8 template-driven form encountering a Minimum Length Error

Upon clicking the submit button, I encountered the following error: ERROR TypeError: Cannot read property 'minlength' of null I am unsure why this error is happening. How can I go about resolving this issue? Below is the code from app.componen ...

The timezone plugin in day.js may sometimes generate an incorrect date

For a while, I've been using dayjs in my angular project to convert timestamps from UTC to localtime. However, after my recent update, this functionality stopped working. This isn't the first issue I've encountered with dayjs, so I decided t ...

An error message occurs in TypeScript when trying to access a property that does not exist in an array

I'm having trouble figuring out this issue... I am receiving data from an API and storing it as an array. I'm attempting to access the data in this format... this.data.results[i].datas[j].dataType However, I keep getting the error "property res ...

Imitate a required component in a service

I am currently facing an issue with mocking a dependency in a service. I am not sure what is causing the problem. It's not the most ideal class to test, but I am mainly focused on code coverage. Below is the code for the service: @Injectable() export ...

Discovering all images in Angular

I have a function that stores image data, including the name. Using *ngFor, I am able to fetch this data from the database and display it in boxes. HTML <div class="row tab-pane Galeria"> <div *ngFor="let product of products" (click)="Im ...