Utilizing generics within a function

Can you help me understand why I am getting an error message that says "Type 'AbstractPopup' is not assignable to type T" when using the return statement in the popupFactory(...) method? This code is just a test example for learning how generics work.

function popupFactory<T extends AbstractPopup>(popupType: PopupType, data: {}): T 
{
    var popup: AbstractPopup;

    switch (popupType)
    {

        case PopupType.ConcretePopup:
        {
                popup = new ConcretePopup();
                break;
        }
    }

    return popup;
}

abstract class AbstractPopup
{
    type: PopupType;
}

class ConcretePopup extends AbstractPopup{}

var p = popupFactory<ConcretePopup>(PopupType.ConcretePopup, {});

Answer №1

When discussing a generic function, it should be understood that it operates based on the type parameter provided to it. However, in this particular scenario, the method determines its return type according to the enum parameter rather than the generic parameter. As a result, the method cannot be considered truly generic.

The reason for the compiler error is due to the fact that, as any T derived from

AbstractPopup</code could be passed, there is no guarantee that the returned type will be compatible with the specific <code>T
being used in the function call. For example:

class OtherConcretePopup extends AbstractPopup{}
var p = popupFactory<OtherConcretePopup>(PopupType.ConcretePopup, {})

In this case, the code would technically be valid, but you are returning a ConcretePopup even though the expected return type is OtherConcretePopup.

A straightforward solution to this issue would be to create overloaded versions of the method for each different popup type:

function popupFactory(popupType: PopupType.OtherConcretePopup, data: {}): OtherConcretePopup
function popupFactory(popupType: PopupType.ConcretePopup, data: {}): ConcretePopup
function popupFactory(popupType: PopupType, data: {}): AbstractPopup 
{
    var popup: AbstractPopup = null;
    switch (popupType)
    {
        case PopupType.ConcretePopup:
        {
                popup = new ConcretePopup();
                break;
        }
    }

    return popup;
}

Alternatively, you could pass the constructor itself as an argument and eliminate the need for the switch statement entirely. This approach works best if all constructors have the same parameters and initialization process:

function popupFactory<T extends AbstractPopup>(popupType: new() => T, data: {}): T
{
    var popup = new popupType();
    return popup;
}

var p = popupFactory(ConcretePopup, {})

Answer №2

To avoid using a switch statement, you can utilize the following generic factory method to instantiate any derived class. The new () => T signature signifies a type with no argument constructor, allowing you to directly specify the name of the derived class if it has a default no argument constructor as shown below -

function popupFactory<T extends AbstractPopup>(popupType:  new () => T, data: {}): T 
{
    var newPopup: T;
    newPopup = new popupType();
    return newPopup;
}

abstract class AbstractPopup
{
    //type: PopupType;
}


class ConcretePopup extends AbstractPopup{  
}

var p = popupFactory(ConcretePopup, {}); // creates instance of ConcretePopup class 

However, when your derived class possesses one or more parameters in its constructor and you need to provide parameter values during the instantiation of the class, you can adopt the approach outlined below -

function popupFactory<T extends AbstractPopup>(popupType:  new (obj:Object) => T, data: {}): T 
{
    var newCust: T;
    newCust = new popupType(data);
    return newCust;
}

abstract class AbstractPopup
{
    //type: PopupType;
}


class ConcretePopup extends AbstractPopup{
    public fname: string;
    public lname: string;
    constructor(obj:Object) {
        super();
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                if (key == "fname") {
                    this.fname = obj["fname"];
                }
                if (key == "lname") {
                    this.lname = obj["lname"];
                }
            }
        }       

    }
}
var p = popupFactory(ConcretePopup, {"fname":"Niladri","lname":"D"});
console.log(p.fname); // Niladri
console.log(p.lname); // D

In this scenario, I am passing

popupType:  new (obj:Object) => T
as a type that requires an Object as a parameter in its constructor. Thus, we can supply the necessary properties and their corresponding values as an object through the data parameter. However, the drawback is having to manually extract properties for the derived class as demonstrated in the constructor of the ConcretePopup class above.

You can access a live example by clicking on this working fiddle link provided.

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

What causes the typings for Partial Record to be undefined when using Object.values?

When I retrieve Object.values from a Partial Record, the values consist of a combination of what I anticipated and undefined. const example: Partial<Record<string, number>> = {} const values = Object.values(example) // The type for values is u ...

How can I update a value using a specific key in Angular?

So, I have a string value that I need to pass to another function. For instance, if the string is 'eng', I want it to be converted to 'en'. I'm looking for a solution that does not involve using slice or if statements. I attempted ...

Is it possible to import the identical file twice consecutively using html and typescript?

I encountered an issue with an input element in my HTML file. Here's what it looks like: <input type="file" (change)="receiveFile($event)" id="inputFileButton" hidden /> This input element is designed for users to import files. Wh ...

Display a symbol retrieved from the backend server

After receiving a response from the backend server for my Angular 2/4 application, I am presented with an attribute called "connectionStatus". This attribute indicates the status of a database connection, either as "UP" or "DOWN". In order to display this ...

Clicking on the React Bootstrap Checkbox within the Nav component does not trigger a rerender of the NavItem component

Encountering an unusual issue while using a Nav and NavItem with a Checkbox from React Bootstrap. What I've noticed is that when clicking directly on the checkbox instead of the NavItem button, the checkbox does not re-render correctly even though my ...

Tips for selecting specific types from a list using generic types in TypeScript

Can anyone assist me in creating a function that retrieves all instances of a specified type from a list of candidates, each of which is derived from a shared parent class? For example, I attempted the following code: class A { p ...

"Is there a way to dynamically remap an array only when there are changes

One of the challenges I am facing is with a component called Page, which contains two components - Editor and Preview. Page has an array called items. [ { value: 0, text: 'Item 1' }, ... ] This array items is passed ...

Using axiosjs to send FormData from a Node.js environment

I am facing an issue with making the post request correctly using Flightaware's API, which requires form data. Since Node does not support form data, I decided to import form-data from this link. Here is how my code looks like with axios. import { Fl ...

What is the best way to transform a string into emojis using TypeScript or JavaScript?

Looking to convert emoji from string in typescript to display emoji in html. Here is a snippet of the Typescript file: export class Example { emoji:any; function(){ this.emoji = ":joy:" } } In an HTML file, I would like it to dis ...

What is the best way to implement Angular translation for multiple values in a typescript file, while also incorporating parameters?

this.snackBar.open( `Only files of size less than ${this.fileSizeAllowed}KB are allowed`, this.translate.instant('USER_REG.close'), { panelClass: 'errorSnackbar', ...

Prevent auth0 express middleware from causing server crashes by handling failures silently

I am currently integrating auth0 into a node project for authentication using JWTs. Each request to an authenticated endpoint requires a token, and auth0 provided me with this middleware function: import {auth} from 'express-oauth2-jwt-bearer'; i ...

The component is no longer able to locate the imported element when it is being shared

Recently, I imported a component into the shared module in order to use it across 2 different modules. However, upon recompiling the app, an error message appeared stating that the jodit-editor, which is utilized by the shared component, is not recognized ...

Using TypeScript with Node.js: the module is declaring a component locally, but it is not being exported

Within my nodeJS application, I have organized a models and seeders folder. One of the files within this structure is address.model.ts where I have defined the following schema: export {}; const mongoose = require('mongoose'); const addressS ...

Is there a way to seamlessly share TypeScript types between my Node.js/Express server and Vite-React frontend during deployment?

I'm currently tackling a project that involves a Node.js/Express backend and a Vite-React frontend. My goal is to efficiently share TypeScript types between the two. How should I configure my project and build process to achieve this seamless type sha ...

Array of dynamically typed objects in Typescript

Hello, I am a newbie to Typescript and I recently encountered an issue that has left me stumped. The problem I am facing involves feeding data to a Dygraph chart which requires data in the format [Date, number, number,...]. However, the API I am using prov ...

The node command line does not recognize the term 'require'

My Typescript project was compiling and running smoothly until recently when I started encountering the error ReferenceError: require is not defined every time I try to run node. I am uncertain whether this issue stems from Typescript, as even when I ru ...

Error Encountered (TypeError): Unable to access attributes of undefined (attempting to read 'appendChild')

I have been working on creating a choropleth Map of AntV using React.js with functional components. This is the code snippet for my chart: import DataSet from '@antv/data-set'; import { Chart } from '@antv/g2'; const CustomerByRegion = ...

Rearrange an element in an array from last to first position using typescript

I am working with an array that looks like this var column = ["generic complaint", "epidemic complaint", "epidemic1 complaint", "epidemic2 complaint", "bal vivah", "name"] My goal is to move the last element of the array to the first position, resultin ...

How can you expand the class of a library object in Animate CC using Createjs?

I am currently in the process of migrating a large flash application to canvas using Typescript, and I'm facing challenges when it comes to utilizing classes to extend library objects. When working with a class library for buttons, class BtnClass { ...

Angular: Elf facade base class implementation for utilizing store mechanics

What is the most effective way to access the store within a facade base class, allowing for the encapsulation of commonly used methods for interacting with the store? Imagine we have a store (repository) export class SomeRepository { private readonly s ...