str1ct
str1ct

Reputation: 33

Typescript - Generics? Or something else?

I have a problem which produces these errors on the console.

Type 'WebFieldB' does not satisfy the constraint 'WebFieldA & WebFieldB'.

There are 2 classes generated by server, which shouldn't be changed.

class WebFieldA {
  readonly Name: string;
  readonly IsUpdatable: boolean;
}

class WebFieldB {
  readonly Name: string;
}

And this is my code:

interface IFieldInfo {
    name: string;
    isUpdatable: boolean;
}

class SomeField<T extends  WebFieldA & WebFieldB> implements IFieldInfo {
    field: T;

    constructor(pField: T) {
        this.field = pField;
    }

    get name(): string {
        return this.field.Name;
    };

    get isUpdatable(): boolean {
        return this.field.IsUpdatable || false;
    }
}

let field1: IFieldInfo = new SomeField<WebFieldA>({Name: 'fieldA', IsUpdatable: false});
let field2: IFieldInfo = new SomeField<WebFieldB>({Name: 'fieldB'}); **// Here for WebFieldB is error**
let myCollection: Array<IFieldInfo> = [field1, field2];

What do I expect? I would like to have a collection of IFieldInfo built from objects of 2 types: WebFieldA and WebFieldB (WFB has no isUpdatable property!). Unfortunately i can modify only my code, not WebFieldA or WebFieldB.

And maybe another idea, to make this without generics? I don't know...

Upvotes: 0

Views: 78

Answers (2)

jcalz
jcalz

Reputation: 327994

As @recursive suggests, your input field is going to be a WebFieldA or a WebFieldB, not a WebFieldA and a WebFieldB. That means you should use WebFieldA | WebFieldB instead of WebFieldA & WebFieldB.

The implementation of isUpdatable is then a problem because it assumes there is an IsUpdatable field on this.field. You can solve this with an appropriate type guard. Don't use instanceof as in @recursive's answer, unless you only plan to pass in actual instances of WebFieldA and WebFieldB as opposed to objects with the same properties. I see that your example code is using plain objects, so you should not use instanceof.

One type guard I like to use is the following useful function (it should probably be in a library):

function hasKey<K extends string>(key: K, obj: any): obj is {[P in K]: any} {
  return key in obj;
}

This just makes sure that an object has a particular key. Then you can implement isUpdatable without errors like this:

get isUpdatable(): boolean {
    return hasKey('IsUpdatable',this.field) ? this.field.IsUpdatable : false;
}

This is a bit much, though. Let's sidestep the type guard issue. Notice that structurally speaking, WebFieldA | WebFieldB is very similar to a single interface like

interface SomeWebField {
  readonly Name: string;
  readonly IsUpdatable?: boolean; // optional
}

and notice that you're not making use of generics at all. You might as well just replace the generic with SomeWebField:

class SomeField implements IFieldInfo {
    field: SomeWebField;

    constructor(pField: SomeWebField) {
        this.field = pField;
    }

    get name(): string {
        return this.field.Name;
    };

    get isUpdatable(): boolean {
        return this.field.IsUpdatable || false;
    }
}

let field1: IFieldInfo = new SomeField({Name: 'fieldA', IsUpdatable: false});
let field2: IFieldInfo = new SomeField({Name: 'fieldB'}); 
let myCollection: Array<IFieldInfo> = [field1, field2];

Assuming you don't have other hidden use cases you haven't mentioned, the above should meet your needs without generics.

Hope that helps; good luck!

Upvotes: 2

recursive
recursive

Reputation: 86084

I'm not exactly sure if this will accomplish your end goal, but you can get your code to compile if you change it like this.

class SomeField<T extends (WebFieldA | WebFieldB)> implements IFieldInfo {
    // ....
    get isUpdatable(): boolean {
        return this.field instanceof WebFieldA && this.field.IsUpdatable;
    }
}

I made a few changes. First I used | instead of & to indicate that T only needs to extend one of the classes, and not both. Then, I added a type guard to the isUpdatable property getter, to ensure the property is only read if T is a WebFieldA or a subclass thereof.

Upvotes: 1

Related Questions