What is the process for generating an object type that encompasses all the keys from an array type?

In my coding journey, I am exploring the creation of a versatile class that can define and handle CRUD operations for various resources. The ultimate goal is to have a single generic class instance that can be utilized to generate services, reducer slices, and components effortlessly, enabling automatic data display, filtering, and pagination for a specific resource.

For this particular scenario, here is what I aim to accomplish:

The Resource City is linked to state and country as parent entities. When calling CityInstance.getPath with IDs for state and country, it should return /country/1/states/1/cities.

export default class Resource {
  name: string;
  path: string;
  parents: string[];

  constructor(arg: { name: string; path: string; parents: string[] }) {
    this.name = arg.name;
    this.path = arg.path;
    this.parents = arg.parents;
  }
//Requirement: Include all parent elements and their respective IDs in parentIds
  getBaseUrl(parentIds: {[parentName:string]: id}){
       const parentPath = this.parents.map(p=>`/${p}/${parentIds[p]}`).join("");

       return `${parentPath}/${this.path}`
  }

}


const resource = new Resource({name:"city", parents: ["countries", "states"]})
//Next call must provide IDs object, disallowing calls without them like this one

resource.getBaseUrl({}) // Should not be allowed by Typescript


resource.getBaseUrl({country: 1}) // Should also not be allowed by Typescript.


//Valid call since it provides both state and country IDs

resource.getBaseUrl({country: 1, state:2});

While working on this project, I encountered challenges with TypeScript's type inference. Even though runtime values cannot be predicted, I attempted to define parents as a type but faced obstacles in implementation.

class Resource<ResourceType, Parents extends string[] =[]> {
    name:string
    resourceUrlName:string
    
    constructor(name:string,resourceUrlName:string){
        this.name=name
        this.resourceUrlName = resourceUrlName
    }
    //How can I specify that the indexer should adhere to the parents array type?
    generateBasePathForCollection(parents: {[name: keyof Parents}: number]){
      // How do I access all members of the Parents Array?

    }
}

Answer №1

According to my understanding, it's not possible to apply mapped types directly to the values of an array. However, you can alter the signature slightly to achieve a similar result. Mapped types can be used effectively with object keys.

type WithParents<T extends Record<string, number>> = {
  baseUrl?: string;
  name: string;
  parents: T;
};

class Resource<T extends Record<string, number>> {
  baseUrl?: string;
  name: string;
  parents: T;

  constructor(args: WithParents<T>) {
    this.baseUrl = args.baseUrl;
    this.name = args.name;
    this.parents = args.parents;
  }

  getBaseUrl(input: { [P in keyof T]: string | number }) {
    const parts = Object.keys(this.parents).sort(k => this.parents[k]);
    return [this.baseUrl, ...parts.map(key => `${key}/${input[key]}`)]
      .filter(Boolean)
      .join("/");
  }
}

The key elements here are the generic constraints on the class and the WithParents type, as well as the mapped type argument (derived from the class's generic argument) in the getBaseUrl method. For more information on mapped types, refer to the documentation: https://www.typescriptlang.org/docs/handbook/advanced-types.html#mapped-types

Subsequently

const x = new Resource({ name: "string", parents: { countries: 1, state: 2 } });

console.log(x.getBaseUrl({ countries: 1, state: 2 })); // countries/1/state/2 
console.log(x.getBaseUrl({ state: 1, countries: 2 })); // countries/2/state/1 

const y = new Resource({ name: "string", parents: { state: 1, countries: 2 } });

console.log(y.getBaseUrl({ countries: 1, state: 2 })); // state/2/countries/1 
console.log(y.getBaseUrl({ state: 1, countries: 2 })); // state/1/countries/2 

You have the option to enhance the usability by specifying types like

{ countries: "number" }
and implementing validation logic accordingly. Currently, any value can be assigned to the record entry without restriction.

Edit: Revised to maintain a specific order. The initial values for parents are utilized to indicate the sequence (starting from 1).

Answer №2

Revised to maintain guardians

guardians is presented as an entity of the identical kind as guardianIds under G. The contents of guardians are inconsequential except for their categories. The sequence of the keys remains intact.

export default class Resource<G extends { [guardianName: string]: number }> {
    title: string;
    destination: string;
    guardians: G;

    constructor({ title, destination, guardians }: { title: string; destination: string; guardians: G }) {
        this.title = title;
        this.destination = destination;
        this.guardians = guardians;
    }
    // It is essential that the parameter guardianIds encompasses all elements from guardians along with their identifiers
    getBaseLink(guardianIds: G) {
        const guardianRoute = Object.keys(this.guardians).map(g => `/${g}/${guardianIds[g]}`).join("");

        return `${guardianRoute}/${this.destination}`
    }

}


const resource = new Resource({ title: "town", destination: "remember-path", guardians: { nation: 0, region: 0 } })
//The upcoming invocation should demand me to input the ids' object and should not permit calls without it like this one

resource.getBaseLink({}) // Typescript should prevent


resource.getBaseLink({ nation: 1 }) // Typescript should restrict.


//This would be accepted since both state and country are provided

resource.getBaseUrl({ nation: 1, region: 2 });

Take note that {[guardianName:string]=> id} is unacceptable as => must be replaced with : and

id</code needs to be a type instead.</p>
<p>Additionally, remember to incorporate <code>destination
when invoking the constructor.

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

The type '{} is not compatible with the type 'IProps'

In my current project, I am utilizing React alongside Formik and TypeScript. The code snippet below demonstrates my usage of the withFormik Higher Order Component (HOC) in my forms: import React from 'react'; // Libraries import........ import { ...

Leveraging the power of both TypeScript 2.3 and 2.4 concurrently within Visual Studio 2015.3 on a single machine

Can TS 2.3 and TS 2.4 be used on the same machine simultaneously? For example, I have one project in VS 2015.3 compiling with TS 2.3 and another project compiling with the latest TypeScript version (TS 2.4). I recently installed TypeScript 2.4, which aut ...

How Angular can fetch data from a JSON file stored in an S3

I am attempting to retrieve data from a JSON file stored in an S3 bucket with public access. My goal is to parse this data and display it in an HTML table. http.get<Post>('https://jsonfile/file.json').subscribe (res => { cons ...

Guide to setting a dynamic value for an input List property in Angular

How can I render multiple dropdowns in Angular based on the response from an API? Currently, when I retrieve data from the API, I am seeing the same information displayed in both dropdown controls. Is there a way to assign dynamic values to the "list" prop ...

Developing a TypeScript frontend library

I am currently in the process of creating a frontend library consisting of React components and CSS/SCSS. Specifically, I am looking for: To build a single CommonJS .js file and .css file. To exclude external libraries (e.g. React) from the .js output. ...

Setting up ESLint for TypeScript with JSX configuration

I am encountering problems with TypeScript configuration. Below is the code snippet from my tsconfig.json: { "compilerOptions": { "target": "es5", "lib": [ "dom", "dom.iterable", "esnext" ], "allowJs": true, "skipLib ...

Having trouble changing file names in a Next.js 13 project

I've been facing an issue ever since Next.Js 13 updated the `pages` folder to a new `app` folder. Whenever I try to rename the default "Pages.tsx" file to something like "Home.tsx" or "Information.tsx", it breaks and shows a 404 Page error. The first ...

Angular mistakenly redirects to the local host at port 4200 with a hash symbol

After using this.router.navigate(['newcard']);, the URL loads correctly but then quickly redirects to localhost:4200/#. This is how I am redirecting from the showcard component to the newcard component: <a href="#" class="btn b ...

What causes the object type to shift away from 'subscribe'?

Currently, I am facing an issue with retrieving a Coupon object from the server using a REST API in combination with Angular. The problem arises when I attempt to access the 'subscribe' method - within the 'subscribe', the object is of ...

Is it possible for Angular templates to be dynamic?

In my component, I have a variable named "myVar" that determines which ng-template should be displayed. Let's consider the following example of a component template: <div *ngIf="myVar; then myVar; else nothing"></div> <ng-template #foo ...

An error encountered while trying to utilize the npm convert-units package within an Ionic 4 application

In my Ionic 4 app, I am utilizing version 2.3.4 of the npm package called convert-units. To install this package in my Ionic 4 application, I used the CLI command: npm i convert-units --save However, upon importing the library with import { convert } fro ...

Angular project service file experiencing issues with TypeScript string interpolation functionality

Here is the code snippet for a service in an Angular project: @Injectable() export class FetchDataService { fetch(link){ console.log('This is a ${link}'); } } In my component, I am invoking this method with a string parameter. Upon che ...

What is the issue with assigning type {intrinsicattributes & true} or type {intrinsicattributes & false} in a React and TypeScript environment?

I am facing an issue with the following code snippet: function Parent() { const count1 = 2; const count2 = 4; const isCount = count1 < 0 || count2 < 0; //setting isCount here return show ? ( <Dialog> ...

Upon running `npm run build` in vue.js, an error occurs stating that the interface 'NodeRequire' cannot extend types 'Require' simultaneously

ERROR in C:/phpStudy2018/PHPTutorial/WWW/Tms.Web/node_modules/@types/node/globals.d.ts(139,11): 139:11 The 'NodeRequire' interface cannot extend both 'Require' and 'RequireFunction' at the same time. The named property &apos ...

A TypeScript utility type that conditionally assigns props based on the values of other properties within the type

There is a common need to define a type object where a property key is only accepted under certain conditions. For instance, consider the scenario where a type Button object needs the following properties: type Button = { size: 'small' | &apo ...

The error message 'tagName' is not a valid property for type ChildNode in Typescript

When I loop over childNodes from a parent node, I encounter an issue while trying to access the tagName of the child nodes. The error message states that tagName does not exist on type ChildNode. const contentParsed = new DOMParser().parseFromString(conte ...

A guide to implementing angularjs app.service and $q in typescript

I am fairly new to TypeScript and AngularJS and I am struggling to find the correct answer for my issue. Below is the relevant code snippet: export class SidenavController { static $inject = ['$scope', '$mdSidenav']; constructor(p ...

Typescript struggling to load the hefty json file

Currently, I am attempting to load a JSON file within my program. Here's the code snippet that I have used: seed.d.ts: declare module "*.json" { const value: any; export default value; } dataset.ts: import * as data from "./my.json" ...

What could be causing the issue where only one of my videos plays when hovered over using UseRef?

I'm currently working on a project where I have a row of thumbnails that are supposed to play a video when hovered over and stop when the mouse moves out of the thumbnail. However, I've encountered an issue where only the last thumbnail plays its ...

The pathway specified is untraceable by the gulp system

Hey there, I've encountered an issue with my project that uses gulp. The gulpfile.js suddenly stopped working without any changes made to it. The output I'm getting is: cmd.exe /c gulp --tasks-simple The system cannot find the path specified. ...