Function in Typescript that accepts an array or a single instance of a constructor and then returns a list

UPDATE:: reproducible link to the TypeScript playground

I have also found a solution in the provided link, but I am still open to learning why my initial approach didn't work.

TLDR;

This method does not yield the expected results

getEntitiesByComponent<T extends Component<any>>(compType: new (...args: any[]) => T | Array<new (...args: any[]) => T>): IEntity[] {

However, this alternative does work

getEntitiesByComponent<T extends new (...args: any[]) => Component<any>>(compType: T | Array<T>): IEntity[]


UPDATE 2

Code snippet from the reproducible link

export interface IComponent {
  setEntity: (value: IEntity) => void;
  getEntity: () => IEntity;
}

export interface IEntity {
    components: any[];
}

export const entitySet = new Set<IEntity>();

export class Component<T extends object> implements IComponent {
  private _entity: IEntity | undefined;
  
  setEntity(value: IEntity) {
    if (this._entity === value) {
      return;
    }
    
    this._entity = value;
  }
  
  getEntity(): IEntity {
    return this._entity!;
  }
  
  props: T;
  
  constructor(props?: T) {
    this.props = {
      ...props as T
    }
  }
}

export class ViewComponent extends Component<any> {
  constructor(props: any) {
    super(props);
  }
}

export class TransformComponent extends Component<any> {
  constructor(props?: any) {
    super();
  }
}

// Typing issue here
function getEntitiesByComponent<T extends Component<any>>(compType: new (...args: any[]) => T | Array<new (...args: any[]) => T>): IEntity[] {
  const compTypes = Array.isArray(compType) ? compType : [compType];
  return Array.from(entitySet.values())
    .filter(e => {
      return e.components.some(c => compTypes.some(compType => c instanceof compType))
    });
}

// This works fine
function getEntitiesByComponentThatWorks<T extends new (...args: any[]) => Component<any>>(compType: T | Array<T>): IEntity[] {
    const compTypes = Array.isArray(compType) ? compType : [compType];
    return Array.from(entitySet.values())
        .filter(e => e.components.some(c => compTypes.some(compType => c instanceof compType)))
}

// error
const viewComponentEntities = getEntitiesByComponent([ViewComponent, TransformComponent]);

// no error
const viewComponentEntitiesWorks = getEntitiesByComponentThatWorks([ViewComponent, TransformComponent]);

ORIGINAL POST

I am attempting to create a function that can accept either a single instance or an array of instances of objects that inherit from a common parent class.

// EntityManager.ts

/**
 * Function designed to receive either a single instance of a constructor returning a
 * Component<any> or an array of similar and should deliver entities containing a
 * component based on the input instances.
**/
getEntitiesByComponent<T extends Component<any>>(compType: new (...args: any[]) => T | Array<new (...args: any[]) => T>): IEntity[] {
  const compTypes = Array.isArray(compType) ? compType : [compType];
  return Array.from(this._entitySet.values())
    .filter(e => {
      return e.components.some(c => compTypes.some(compType => c instanceof compType))
    });
}

The classes ViewComponent and TransformComponent are both extensions of Component.

// ViewComponent.ts

import { Component } from '../components/component';

export type ViewComponentProps = {
  alias: string;
  src: string;
}

export class ViewComponent extends Component<ViewComponentProps> {
  constructor(props: ViewComponentProps) {
    super(props);
  }
}
// TransformComponent.ts
import { Component } from './component';

export type TransformComponentProps = {
  x: number;
  y: number;
  scaleX?: number;
  scaleY?: number;
  zIndex?: number;
}

export class TransformComponent extends Component<TransformComponentProps> {
  private static _defaultProps: TransformComponentProps = {
    x: 0,
    y: 0,
    scaleX: 1,
    scaleY: 1,
    zIndex: 0,
  };
  
  constructor(props?: TransformComponentProps) {
    super({
      ...TransformComponent._defaultProps,
      ...props
    });
  }
}

When I call the function like this

const viewComponentEntities = EntityManager.getInstance().getEntitiesByComponent([ViewComponent, TransformComponent]);

TypeScript throws the following error

TS2345: Argument of type
(typeof TransformComponent | typeof ViewComponent)[]
is not assignable to parameter of type
new (...args: any[]) => Component<any> | (new (...args: any[]) => Component<any>)[]
Type
(typeof TransformComponent | typeof ViewComponent)[]
does not match the signature
new (...args: any[]): Component<any> | (new (...args: any[]) => Component<any>)[]

I am unsure why this error is occurring and how to resolve it.

Answer №1

Thanks to @jcalz's assistance, I was able to identify a syntax error in my code.

getEntitiesByComponent<T extends Component<any>>(compType: new (...args: any[]) => T | Array<new (...args: any[]) => T>): IEntity[]

I had mistakenly been looking for a constructor that returned T or

Array<new (...args: any[]) > T>

instead of just a single constructor (new (...args: any[]) => T) or an array of them

Array<new (...args: any[]) > T>

Now that this issue has been corrected,

getEntitiesByComponent<T extends Component<any>>(compType: (new (...args: any[]) => T) | Array<new (...args: any[]) > T>): IEntity[]

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

Removing undefined elements from an array

Could somebody clarify why, in this particular scenario: const dataValues: ValueRange[] = res.data.valueRanges.filter((range: ValueRange) => range.values); const formattedValues: Array<SheetData | undefined> = dataValues.map(this.formatSheetRang ...

Convert a regular element into a DebugElement within an Angular framework

Recently, I was working on testing an Angular Component which was going smoothly until I encountered a challenging issue that has been perplexing me for days. My main objective was to test whether the method "ajouterCompteurALaCampagne" is being called whe ...

Creating objects based on interfaces

After looking at this straightforward code: interface int1 { aa: string, bb: number, } const obj1:int1 = {} //#1 function fun(param_obj:int1) { //#2 } I am curious as to why the compiler throws an error: Type '{}' is missing the fol ...

Is it possible to integrate TypeScript 5.0 decorators into React components?

Every time I add decorators to my class, they always get called with the arguments specified for legacy decorators: a target, property key, and property descriptor. I am interested in using TypeScript 5.0 decorators. Is this feasible, and if so, how can I ...

The Google APIs sheet API is throwing an error message stating "Invalid grant: account not found"

I need to retrieve data from a spreadsheet using the Sheet API. After setting up a project in Google Cloud Platform and creating a service account, I granted the account permission to edit the spreadsheet. I then downloaded the credentials in JSON format. ...

Create a pipeable stream that does not trigger any events when data is piped

I have been trying to utilize the renderToPipeableStream function from React18, and although it is functional, I am struggling with handling the pipe properly. The key section of my code involves an array of strings representing HTML. I am splitting the s ...

The deletion by index feature seems to be malfunctioning in Angular

Is there a way to delete an item by its ID instead of just deleting the last element using this code? I want to create input fields with a delete button next to each one simultaneously. TS: public inputs: boolean[] = []; public addNew(): void { this ...

What are some creative ways to emphasize certain dates?

Is there a way to customize mui-x-date-pickers to highlight specific days from a Date array with green filled circles around them? I am using new Date and wondering how to achieve this effect. Below is the code snippet I am currently working with: <Dat ...

One creative method for iterating through an array of objects and making modifications

Is there a more efficient way to achieve the same outcome? Brief Description: routes = [ { name: 'vehicle', activated: true}, { name: 'userassignment', activated: true}, { name: 'relations', activated: true}, { name: &apos ...

Enhancing Angular input validators with updates

Working on a project with Angular 6, I have set up an input field using mat-input from the Angular Material framework and assigned it an id for FormGroup validation. However, when I initialize my TypeScript class and update the input value, the validator d ...

Creating a primary index file as part of the package building process in a node environment

Currently, I have a software package that creates the following directory structure: package_name -- README.md -- package.json ---- /dist ---- /node_modules Unfortunately, this package cannot be used by consumers because it lacks an index.js file in the r ...

Guidelines for transmitting form information to a web API using Angular

I am currently working on an Angular 6 project where I have a form and a table that retrieves data from a web API. I want to know if it's possible to send the form data to that web API. Here is the code snippet that I have so far: HTML Form: &l ...

combine string inputs when ng-click is triggered

Is there a way to pass a concatenated string using ng-click to MyFunction(param: string)? I have tried but so far, no luck: <input id="MeasurementValue_{{sample.Number}}_{{$index}}" ng-click="Vm.MyFunction('MeasurementValue_{{sample.Number ...

The MUI theme seems to be missing its application

As a newcomer to MUI, I'm facing challenges when trying to apply a custom theme. My goal was to create a new variant for the button using the code snippet below: // @ts-nocheck import React, {FC} from 'react'; import { createTheme, ThemeProv ...

Display a single unique value in the dropdown menu when there are duplicate options

Hey there, I'm currently working on retrieving printer information based on their location. If I have multiple printers at the same location, I would like to only display that location once in the dropdown menu. I am aware that this can be resolved at ...

Issue with Moment.js: inability to append hours and minutes to a designated time

I have a starting time and I need to add an ending time to it. For example: start=19:09 end=00:51 // 0 hours and 51 minutes I want to add the 51 minutes to the 19:09 to make it 20:00. I've attempted several different methods as shown below, but none ...

Tips for showing that every field in a typed form group must be filled out

Starting from Angular 14, reactive forms are now strictly typed by default (Typed Forms). This new feature is quite convenient. I recently created a basic login form as shown below. form = this.fb.group({ username: ['', [Validators.required ...

Error in Typescript: Array containing numbers is missing index property `0`

This is the code for my class: class Point{ coordinates: [number, number, number]; constructor(coordinates: [string, string, string]) { this.coordinates = coordinates.map((coordinate) => { return Math.round(parseFloat(coordinate) *100)/ ...

Enhancing supertest functionality with Typescript

Currently, I am working on extending the functionality of supertest. After referencing a solution from Extending SuperTest, I was able to implement the following example using javascript: const request = require('supertest'); const Test = reque ...

Using `useState` within a `while` loop can result in

I'm working on creating a Blackjack game using React. In the game, a bot starts with 2 cards. When the user stands and the bot's card value is less than 17, it should draw an additional card. However, this leads to an infinite loop in my code: ...