Gustavo Lopes
Gustavo Lopes

Reputation: 4194

Casting in generics - Typescript

I have a base class:

class ClassA {
    public prop1: string;
}

I have a generic interface:

interface IMyInterface<T extends ClassA> {
    objA: T;
}

And a generic class:

class MyClass<T extends ClassA> implements IMyInterface<T> {
    public objA: T;

    public myMethod () {
        this.objA.prop1 = "foo"; // This is ok

        if ("prop2" in this.objA) {
            this.objA.prop2 = "bar"; // This is not
        }
    }
}

How can I force the prop2 in objA if, and only if, this property exists in objA?

Do I have to force a cast as (this.objA as ClassA & {prop2: string}).prop2 = "bar"?

Upvotes: 0

Views: 1103

Answers (2)

Jeena
Jeena

Reputation: 46

You can use type guards, as described here, inside the Type Guards and Differentiating Types session

Your code would be something like this:

class ClassA {
    public prop1: string;
}

interface IMyInterface<T extends ClassA> {
    objA: T;
}

interface IProp2 {
    prop2: string;
}

function hasProp2(obj: ClassA | IProp2): obj is IProp2 {
    return (<IProp2>obj).prop2 !== undefined;
}

class MyClass<T extends ClassA> implements IMyInterface<T> {
    public objA: T;

    public myMethod () {
        this.objA.prop1 = "foo"; // This is ok

        if (hasProp2(this.objA)){
            this.objA.prop2 = "bar"; // Now this is ok
        }
    }
}

Upvotes: 1

CRice
CRice

Reputation: 32266

This appears to be a current limitation of Typescript. I found this issue describing this exact case.

The workaround is the same as what you've already mentioned. You'll have to cast this.objA to some other type that permits setting a prop2 field. So:

(this.objA as ClassA & {prop2: string}).prop2 = "bar"

// Or to trade some safety for brevity:
(this.objA as any).prop2 = "bar"

Otherwise, you can also use a custom type guard to assert the existence of that key. Using the one from the issue linked above, you can do something like this:

function inOperator<K extends string, T>(k: K, o: T): o is T & Record<K, any> {
    return k in o;
}

Then, using that instead of the in operator:

if (inOperator("prop2", this.objA)) {
    this.objA.prop2 = "bar"; // No error now
}

Upvotes: 2

Related Questions