The function Func<T extends IComponent>( ) returns an array of type T that extends IComponent, which ultimately results in an array of IComponent[] without explicitly assigning

Currently, I am attempting to implement the ECS pattern in TypeScript. I have created a class called ComponentStore that contains components of entities for future handling purposes. Here is an example of what these components look like:

class Health implements IComponent {
  name: EComponents = EComponents.health;
}

However, when using the methods setComponent and getComponents, I encounter the following errors:

Error:(12, 11) TS2322: Type 'IComponent[]' is not assignable to type 'T[]'. Type 'IComponent' is not assignable to type 'T'.

Error:(17, 5) TS2322: Type 'IComponent[]' is not assignable to type 'T[]'. Type 'IComponent' is not assignable to type 'T'.

I followed an example from the Generics in Typescript documentation, but it did not solve this issue.

class ComponentStore implements IComponentStore {
  private components: Map<EComponents, IComponent[]> = new Map();

  setComponent<T extends IComponent>( componentName: EComponents, component: IComponent): void {
    const components: T[] = this.components.get(componentName) || [];
    this.components.set(componentName, [...components, component ]);
  }

  getComponents<T extends IComponent>( componentName: EComponents): T[] {
    return this.components.get(componentName) || [];
  }
}

enum EComponents {
  health = 'health',
}

interface IComponent {
  name: Ecomponents;
}

Although I can use casts as a workaround, I believe there should be a better solution. My intention was for the type detection to function correctly with these methods. I aim to set up a Map with a specific name of EComponent which stores an array of one type, such as IHealth, and then transfer it to the system.

Answer №1

I'm not entirely certain about your requirements, but you might find the following code snippet helpful:

interface EComponents {
  //...
}

interface IComponent {
  //...
}

interface IComponentStore<T extends IComponent> {
  setComponent(componentName: EComponents, component: T): void;
  getComponents(componentName: EComponents): T[];
}

class ComponentStore<T extends IComponent> implements IComponentStore<T> {
  private components = new Map<EComponents, T[]>();

  setComponent(componentName: EComponents, component: T): void {
    const components = this.components.get(componentName) || [];
    this.components.set(componentName, [...components, component ]);
  }

  getComponents(componentName: EComponents): T[] {
    return this.components.get(componentName) || [];
  }
}

The concept here is that ComponentStore and IComponentStore are already configured to handle instances of generic type T for IComponent...

I assumed that IComponent and EComponents are interfaces. Placeholder implementations have been provided for them. This code should compile without any issues. It compiles to the following JavaScript (targeting ESNext):

"use strict";
class ComponentStore {
    constructor() {
        this.components = new Map();
    }
    setComponent(componentName, component) {
        const components = this.components.get(componentName) || [];
        this.components.set(componentName, [...components, component]);
    }
    getComponents(componentName) {
        return this.components.get(componentName) || [];
    }
}

I hope this proves useful to you.

Edit:

Additional note, IComponentStore does not necessarily need to be generic for this scenario. The following code variation also functions correctly:

interface EComponents {
  //...
}

interface IComponent {
  //...
}

interface IComponentStore {
  setComponent(componentName: EComponents, component: IComponent): void;
  getComponents(componentName: EComponents): IComponent[];
}

class ComponentStore<T extends IComponent> implements IComponentStore {
  private components = new Map<EComponents, T[]>();

  setComponent(componentName: EComponents, component: T): void {
    const components = this.components.get(componentName) || [];
    this.components.set(componentName, [...components, component ]);
  }

  getComponents(componentName: EComponents): T[] {
    return this.components.get(componentName) || [];
  }
}

This version translates to the same JavaScript output.

Edit 2

It has been five days since my last update, so I wanted to make good on my promise to incorporate any new information from your feedback. My apologies for the delay.

In case you haven't found a suitable solution yet, I have included another code example below that could serve as inspiration. I hope it offers some assistance.

enum EComponents {
  health = 'health',
  wealth = 'wealth'
}

interface IComponent {
  name: EComponents;
  value: string;
}

interface IComponentStore {
  setComponent<T extends IComponent>(component: T): void;
  getComponents<T extends IComponent>(componentName: EComponents): T[];
}

class ComponentStore implements IComponentStore {
  private components: Map<EComponents, IComponent[]> = new Map();

  setComponent<T extends IComponent>(component: T): void {
    const components: IComponent[] = this.components.get(component.name) || [];
    this.components.set(component.name, [...components, component]);
  }

  getComponents<T extends IComponent>(componentName: EComponents): T[] {
    return (this.components.get(componentName) || []) as T[];
  }
}

class Health implements IComponent {
  name: EComponents = EComponents.health;

  constructor(public value: string) {
  }
}

class Wealth implements IComponent {
  name: EComponents = EComponents.wealth;

  constructor(public value: string) {
  }
}

const store = new ComponentStore();

store.setComponent(new Health('ill'));
store.setComponent(new Health('fairly healthy'));
store.setComponent(new Health('top condition'));

store.setComponent(new Wealth('poor'));
store.setComponent(new Wealth('prosperous'));
store.setComponent(new Wealth('filthy rich'));

console.log(store.getComponents(EComponents.health));
console.log(store.getComponents(EComponents.wealth));

Answer №2

I made a transformation to IComponent and turned it into a generic.

interface IComponent<T> {
  name: EComponents;
  id: string;
}

class ComponentStore implements IComponentStore {
  private components: Map<EComponents, IComponent<any>[]> = new Map();

  setComponent<T extends IComponent<T>>( componentName: EComponents, component: IComponent<T>): void {
    const components: IComponent<T>[] = this.components.get(componentName) || [];
    this.components.set(componentName, [...components, component ]);
  }

  getComponents<T extends IComponent<T>>( componentName: EComponents): IComponent<T>[] {
    return this.components.get(componentName) || [];
  }
}

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

Optimal asset management strategies for Angular applications

What is the process for loading assets in an Angular app? Will the app wait for all assets to load before bootstrapping, or will they be lazy loaded if not needed on the initial page? I have a large number of PDFs stored in the assets folder that I load a ...

Collection of personalized forms where the parent is a FormGroup

One scenario I'm working on involves creating multiple custom formgroup classes that have FormGroup as their parent class, structured like this: export class CustomFormGroup1 extends FormGroup { //custom properties for this FormGroup const ...

Updating object values within a React array - a step-by-step guide

My development stack includes the following technologies: ・ react ・ typescript I have been trying to update the member object in the state array. Here is how I tried to implement it, but unfortunately encountered an error: Error Message: Type &a ...

Transform a YAML document into either a JSON blueprint or a TypeScript structure

Currently seeking an efficient tool, preferably in Node JS, that can convert a YAML file from swagger into either a JSON SCHEMA or a typescript interface. The process I have used so far is: YAML->RAML->JSON SCHEMA->TYPESCRIPT interface STEP1: Co ...

Getting the FormArray value in an Angular TypeScript file

Having trouble accessing the form array value in my TypeScript file - it's coming up as a blank array. Here's my HTML code: <mat-form-field class="example-full-width" > <mat-label>Locations </mat-lab ...

Is it possible for a conditional type to dictate the type it is connected

I'm currently attempting to utilize conditional types with generics in order to determine another type, but I am facing issues with the narrowing of the conditional type. Specifically, I am looking to narrow down the conditional type based on the para ...

Each Tab in Ionic2 can have its own unique side menu that opens when selected

In my ionic2 app, I wanted to implement a unique side menu for each of my tabs. Here is what I attempted: I used the command ionic start appname tabs --v2 to create the initial structure. Next, I decided to turn both home.html and contact.html (generated ...

Webpack is mistakenly looking in the incorrect subfolder when attempting a relative import

I have set up a Vue application (version 3 with TypeScript) within a directory structure where the Vue app is nested inside a directory named /my-vue-app. In the same directory, there is a folder containing my Node.js server code (not TypeScript) that I am ...

What is the correct way to properly enter a Svelte component instance variable?

Currently, I am delving into learning Svelte and specifically exploring how to bind to a component instance as demonstrated in this tutorial: As I progress through the tutorial, I am attempting to convert it to Typescript. However, I have encountered an ...

Performing a series of Http Get requests in Angular 2 with an array that can

Seeking assistance with an Observable http sequence that involves making two dependent http calls to an api. The first call returns an Array of Urls, and the second call makes get requests for each url in the array and then returns the responses on the str ...

typescript undefined subscription to observable

After making an http request to fetch some data, I am facing issues in displaying it as intended. The dropdown select for entriesPerPage, and the left and right cursors for switching page in pagination are working fine. However, upon switching a page, I en ...

Experiencing difficulty in transferring array information from a parent component to a child component within an

I'm currently working on a task where I need to pass data from a parent component to a child component. The data consists of an array that is nested within another array. parent.component.html <div *ngFor="let parent of parentArray; index as ...

Where specifically in the code should I be looking for instances of undefined values?

One method in the codebase product$!: Observable<Product>; getProduct(): void { this.product$ = this.route.params .pipe( switchMap( params => { return this.productServ.getById(params['id']) })) } returns an ...

Tips for implementing Nested Interface values within the Grid

I am facing an issue trying to retrieve values from a nested interface. Currently, I am only getting null for the nested interface values, while other values are being retrieved successfully. import {CyAndNY} from "./CyAndNYInterface"; export interface G ...

Tips for concealing the delete button within the main content area and displaying it in the subsequent "add" pop-up window upon clicking the "add" button in Angular

I have a card with add and delete buttons. I want only the add button to show initially, and when clicked, a new card should open with both add and delete buttons. Everything is working as expected except I can't figure out how to hide the delete butt ...

Using kdbxweb for generating databases, saving, and accessing passwords to be utilized in scripts and tasks

Struggling to grasp the concepts behind the kdbxweb library, I find myself unable to navigate the documentation due to my lack of prerequisite knowledge. It seems the information provided is geared towards users with a certain level of understanding that I ...

Angular: Connecting template data to different visual presentations

Looking for a solution to display data and map values to another presentation without needing complex ngIf statements or creating multiple components. Check out this sample: https://stackblitz.com/edit/angular-9l1vff The 'vals' variable contain ...

SVG polyline animation that persists seamlessly

In my TypeScript code, I have implemented a function that creates a polyline based on 4 different points (x1y1, xCy1, xCy1, x2y2), where Xc represents the half distance between x1 and x2 on a plane. This polyline does not form a circular shape. private cre ...

Manipulating Typescript JSON: Appending child attributes and showcasing them alongside the parent item attributes

Here is a JSON data that needs to be processed: var oldArr = [{ "careerLevel": "Associate", "careerLevels": [ { "201609": 21, "201610": 22, "careerID": "10000120" }, { "201609": 31, "201610": 32, ...

Angular: Initiate multiple functions simultaneously and combine results afterwards

My current code successfully zips and saves the response of a JSON array by splitting them into individual files using a single method. zip: JSZip = new JSZip(); folder: JSZip = new JSZip(); this.apicall.api1() .subscribe( response => { for (let r ...