Creating a generic type in TypeScript that represents a union of keys from type T's properties

Is there a way to determine the result type (TTarget) based on TSource and the provided property names (keyof TSource)?

I have this function that copies specified properties to a new object:

export declare type PropertyNamesOnly<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

function CopyProps<TSource, TTarget>(source: TSource, ...props: PropertyNamesOnly<TSource>[]): TTarget {
    const result: any = {};  
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
}

Now we can use it like this:

class Props { a: string = "a"; b: string = "b"; c: string = "c"; }
const props = new Props();

const copy = CopyProps<Props, Omit<Props, "b">>(props, "a", "c");

expect(copy.a).to.equal("a");
// copy has omitted property b
expect((copy as any).b).to.be.undefined;
expect(copy.c).to.equal("c");

However, I would like to avoid defining TSource and TTarget. Instead, I want the following structure:

CopyProps<TSource>(source: TSource, ...props: PropertyNamesOnly<TSource>[]): TypeFromProps<props> {
    const result: any = {};  
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
}

// Then copy should only contain properties 'a' and 'c'
const copy = CopyProps(props, "a", "c");

How can we achieve the TypeFromProps type?

Solution:

static PickProps<
  TSource,
  Props extends PropertyNamesOnly<TSource>,
  TTarget extends Pick<TSource, Props>>
  (source: TSource, ...props: Props[]): TTarget {
    const result: any = {};
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
  }

  static OmitProps<
    TSource,
    Props extends PropertyNamesOnly<TSource>,
    TTarget extends Omit<TSource, Props>>
    (source: TSource, ...props: Props[]): TTarget {
      const result: any = {};
      const keys = Object.keys(source).filter(k => props.some(p => p !== k)) as (keyof TSource)[];

      for (const key of keys) {
          result[key] = source[key];
      }
    }

Answer №1

To accomplish the objective, we must elevate the properties type to a generic level. Here is an example:

declare type PropertyNamesOnly<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

function CopyProps<
  TSource, 
  Props extends PropertyNamesOnly<TSource>, 
  TTarget extends Pick<TSource, Props>>
(source: TSource, ...props: Props[]): TTarget {
    const result: any = {};  
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
}

class Props { a: string = "a"; b: string = "b"; c: string = "c"; }
const props = new Props();

const copy = CopyProps(props, "a", "c"); // resulting object includes properties 'a' and 'c'

// testing with a function as property
const example2 = { a: 'a', b: () => { }, c: 1 };
const copy2 = CopyProps(example2, "a", "b"); // error because 'b' is a function

const example3 = { a: 'a', b: () => { }, c: 1 };
const copy3 = CopyProps(example2, "a", "c"); // successful as it contains 'a' and 'c'

Key points to note:

  • Props extends PropertyNamesOnly<TSource>
    - specifies that props are keys from TSource excluding those with function values
  • TTarget extends Pick<TSource, Props>
    - defines the return object as a subset of TSource based on the specified props
  • ...props: Props[] - the declaration of props ensures proper type inference

Playground link

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

Transforming JavaScript code with Liquid inline(s) in Shopify to make it less readable and harder to understand

Today, I discovered that reducing JavaScript in the js.liquid file can be quite challenging. I'm using gulp and typescript for my project: This is a function call from my main TypeScript file that includes inline liquid code: ajaxLoader("{{ &ap ...

Running JavaScript code when the route changes in Angular 6

Currently, I am in the process of upgrading a website that was originally developed using vanilla JS and JQuery to a new UI built with Angular and typescript. Our site also utilizes piwik for monitoring client activity, and the piwik module was created i ...

Error: Idle provider not found in the promise

Currently, I am integrating ng2-idle into an AngularJS 2 application. After successfully including the ng2-idle package in the node_modules directory of my project, I attempted to import it into one of my components as shown below: Dashboard.component.ts: ...

You may encounter an error stating "Property X does not exist on type 'Vue'" when attempting to access somePropOrMethod on this.$parent or this.$root in your code

When using VueJS with TypeScript, trying to access a property or method using this.$parent.somePropOrMethod or this.$root.somePropOrMethod can lead to a type error stating that Property somePropOrMethod does not exist on type 'Vue' The defined i ...

Tips for exporting a React Component without using ownProps in a redux setup with TypeScript

I'm currently working on a project using typescript with react-redux. In one of my components, I am not receiving any "OwnProp" from the parent component but instead, I need to access a prop from the redux state. The Parent Component is throwing an er ...

The filename is distinct from the file already included solely by the difference in capitalization. Material UI

I have recently set up a Typescript React project and incorporated Material UI packages. However, I encountered an error in VS Code when trying to import these packages - although the application still functions properly. The error message reads: File na ...

Angular does not wait for the backend service call response in tap

Does anyone have a solution for subscribing to responses when the tap operator is used in a service? edit(status) { dataObj.val = status; // call post service with status.. this.service .update(dataObj) .pipe(takeUntil(this._n ...

Utilize data binding in Typescript to easily link variables between a service and controller for seamless utilization

Is there a way to overcome the issue of value assignment not binding data in this scenario? For example, using this.arrayVal = someService.arrayVal does not work as intended. The objective is to simplify the assignment in both HTML and controller by using ...

Error: Prettier is expecting a semi-colon in .css files, but encountering an unexpected token

I'm currently attempting to implement Prettier with eslint and TypeScript. Upon running npm run prettier -- --list-different, I encountered an error in all of my css files stating SyntaxError: Unexpected token, expected ";". It seems like there might ...

Exploring the functionality of surveyjs in conjunction with react and typescript

Does anyone have any code samples showcasing how to integrate Surveyjs with React and TypeScript? I attempted to import it into my project and utilized the code provided in this resource. https://stackblitz.com/edit/surveyjs-react-stackoverflow45544026 H ...

Fixing ngModel and click functionality issues in dynamic HTML content within Angular 4

I am struggling to insert HTML content into a specific id by using Angular. Although the HTML displays, the functionality of ngModel and click event is not working. How do I resolve this issue? app.component.html <div id="myid"> </div> app. ...

Allowing HTML attributes in reusable components with Vue TSX: A guide on informing Typescript

Imagine I have a custom input component: import { defineComponent } from "@vue/runtime-core" export default defineComponent({ inheritAttrs: false, setup(props, { attrs }) { return () => ( <div> ...

Creating a custom pipe that converts seconds to hours and minutes retrieved from an API can be achieved by implementing a transformation function

Can someone please provide guidance on creating a custom pipe in Angular 8 that converts seconds to hours and minutes? Thank you. <div class="col-2" *ngFor="let movie of moviesList"> <div class="movie"> {{ movie.attributes.title }} ...

Using ngIf for various types of keys within a JavaScript array

concerts: [ { key: "field_concerts_time", lbl: "Date" }, { key: ["field_concert_fromtime", "field_concert_totime"], lbl: "Time", concat: "to" }, { key: "field_concerts_agereq", lbl: "Age R ...

React: Why aren't class methods always running as expected?

I created a class component that retrieves a list of applications from an external API. It then sends a separate request for each application to check its status. The fetching of the applications works well, but there is an issue with the pinging process ...

Exploring TypeScript and React: Redefining Type Definitions for Libraries

As I transition from JSX to TSX, a challenge has arisen: My use of a third-party library (React-Filepond) This library has multiple prop types The provided types for this library were created by an individual not affiliated with the original library (@ty ...

Can an Angular 2 module export an interface?

While attempting to export an interface in a NgModule-declaration, I encountered an error message in my editor (Visual Studio Code) stating: [ts] 'MyInterface' only refers to a type, but is being used as a value here. Below is the code snippet c ...

Encountering a Prettier error with React Native 0.71 and Typescript

https://i.stack.imgur.com/h9v5X.pngThe app runs smoothly, but these red warnings are really bothering me. How can I resolve this issue?https://i.stack.imgur.com/NebzJ.png ...

Generate a series of HTTP requests using an HTTP interceptor

Is it possible to initiate an http request before another ongoing http request finishes (For example, fetching a token/refresh token from the server before the current request completes)? I successfully implemented this functionality using Angular 5' ...

Having trouble accessing functions in Typescript when importing JavaScript files, although able to access them in HTML

Recently, I started incorporating TypeScript and React into my company's existing JavaScript code base. It has been a bit of a rollercoaster ride, as I'm sure many can relate to. After conquering major obstacles such as setting up webpack correc ...