0

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...

2 Answers 2

2

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!

Sign up to request clarification or add additional context in comments.

1 Comment

Of course. That helps. I wanted to know if there is another way to do that. And it is so easy... I think that topic is exhausted. Thanks a lot everyone :)
1

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.

2 Comments

You should probably change the type guard to something that tests 'this.field.IsUpdatable' directly (structurally), since the example code is testing against objects that are not instanceof the WebFieldA or WebFieldB classes.
@str1ct, be careful, test against (new SomeField<WebFieldA>({Name: 'fieldA', IsUpdatable: true})).isUpdatable at runtime

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.