0
votes
interface myGenericInterface<T> {
    propX : T;
}

class implementationA implements myGenericInterface<string> {
    propX : string;
}

class implementationB implements myGenericInterface<number> {
    propX : number;
}

class sample<T> {
    prop : myGenericInterface<T>;

    constructor( x : any) {
        if(typeof x === "string"){
            prop = new implementationA(); // Error reported here. how do I achieve this? 
        }
    }
}

Error at line - prop = new implementationA() Types of property 'content' are incompatible. Type 'string' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'string'

What is the right way of using generics in such case? How do I conditionally set the class property having a myGenericInterface type.

2

2 Answers

1
votes

Problem

class sample<T> is a generic class where the type of this.prop depends on the type of the generic type parameter T for that instance.

Typically a generic class is set up so that the type of T for an instance will be inferred from the arguments which are passed to the constructor. Your constructor just has (x : any), so the type of T will always be unknown unless you explicitly set it by calling new sample<string>("").

The argument x is any, so it's fine to call new sample<number>(""). In that case T is number. But typeof x === "string" is true so you will be setting this.prop, which must be myGenericInterface<number> to implementationA which is myGenericInterface<string>. So hopefully you can see why Typescript gives you an error on this line.


Mediocre Solution

I think what you wanted to do was to have x be of type T and to constrain T such that it can only be string or number.

You can do that, but it's not 100% type-safe as it requires us to make an assertion which is probably correct but we can't guarantee it. When we check typeof x === "string" that will refine the type of x to just string, but it won't refine the type of T because it's technically possible that T is the union string | number or that T is a subset of string. So we have to use as to suppress the Typescript error.

class sample<T extends string | number> {
    prop: myGenericInterface<T>;

    constructor(x: T) {
        if (typeof x === "string") {
            // x is known to be string, but we don't know that T is string
            this.prop = new implementationA() as myGenericInterface<T>
        } else {
            this.prop = new implementationB() as myGenericInterface<T>
        }
    }
}

Good Solution

We can guarantee type-safety for any type (not just string | number) if we just create the object which implements myGenericInterface<T> ourselves. If we set propX to x then we don't need to know or care what the actual type of T is because we know that x is assignable to T so {propX: x} is assignable to myGenericInterface<T>.

class sample<T> {
    prop: myGenericInterface<T>;

    constructor(x: T) {
        this.prop = {
            propX: x,
        }
    }
}

You can use this same approach with an extra step by using a generic class to implement myGenericInterface<T>.

class GenericImplementation<T> implements myGenericInterface<T> {
    propX: T;

    constructor(value: T) {
        this.propX = value;
    }
}

class sample<T> {
    prop: myGenericInterface<T>;

    constructor(x: T) {
        this.prop = new GenericImplementation(x);
    }
}

Typescript Playground Link

1
votes

By class sample<T> { you are binding T to some unknown type which might be neither string nor number. And if T is for example number[] (an array), then prop will be of type myGenericInterface<number[]> can not be assigned a value of type myGenericInterface<string>.

What you can do is to change to

class sample {
    prop : myGenericInterface<string|number>;

    constructor( x : any) {
        if(typeof x === "string"){
            this.prop = new implementationA();  // Now works
        }
    }
}

Playground