Reputation: 33
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
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
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