3

Why I get an error "Type 'AbstractPopup' is not assignable to type T" on the return of popupFactory(...) method ? Please ignore other errors this is just a test code to understand better how generic works.

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, {});
2
  • 1
    if you are going to use a generic factory why use a switch case? Commented Mar 30, 2018 at 12:13
  • Your function return type is T. T can be any class that extends AbstractPopup. But you actually return popup, which is of type AbstractPopup. AbstractPopup is not the same type as T. If I call your function using popupFactory<MyCustomPopup>(...), your method is supposed to return an instance of MyCustomPopup. It doesn't. Commented Mar 30, 2018 at 12:58

2 Answers 2

2

A generic function implies it should work based on the generic type parameter passed to it. In your case the method decides the return type based on the enum parameter, not based on the generic parameter. So the method is not really generic.

The reason the compiler complains is that, since you could pass any T derived from AbstractPopup it can't really check that the type you return will be compatible with the specific T you pass to the function. Consider the following valid code:

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

The code would be valid, but you return a ConcretePopup even though the return type is OtherConcretePopup

The simplest solution would be to have several overloads to the method for each 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;
}

Or you could pass the constructor as a parameter and eliminate the need for the switch altogether, but this a good option only if all the constructors have the same number of parameters and the initialization is the same for all:

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

var p = popupFactory(ConcretePopup, {})
Sign up to request clarification or add additional context in comments.

5 Comments

Didn't see your post while typing , I guess I am late here.
@Niladri yeah, but your post has more details on the constructor passing approach, let OP decide which one he wants :-)
ok ,, I added the extra params for constructor top of your last approach
Tnx your reply actually lead me to this solution which looks much less verbose and preserves the type safety. function popupFactory<T extends AbstractPopup>(popupType: PopupType, data: {}): T { var popup: AbstractPopup; switch (popupType) { case PopupType.ConcretePopup: { popup = new ConcretePopup() ; break; } } return popup as any; }
Using a type assertion to any will always work but is raerly a type safe way to do things. As my first example shows you can missmatch the type and the enum and the compiler will not know about it. I strongly recommend one of the other alternative solutions
0

Instead of using switch you can use the below generic factory method to create instance of any derived class . Note the new () => T signature here , it denotes a type with no argument constructor so you can directly pass the derived class name here if it has a default no argument constructor like 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 

But if your derived class have 1 or more parameters in the constructor and you need to set the parameters value while creating the instance of the class you can follow the below approach -

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

Here i am passing popupType: new (obj:Object) => T as a type which takes an Object as parameter in its constructor. So we can pass the required properties and the values as an object which is done by data parameter here . But the drawback for this approach is that you have to manually extract the properties for the derived class as you can see in the ConcretePopup class constructor above.

Here is a link to a working fiddle

Code sample

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.