Reputation: 1237
I have four entities (models) in TypeScript as follows:
Base class/model:
export interface BaseClass {
id: string;
entityId: string;
entityName: string;
}
Child Class 1:
import { BaseClass } from './base-class';
export interface ChildClass1 extends BaseClass {
commentId: string;
authorName: string;
}
Child Class 2:
import { BaseClass } from './base-class';
export interface ChildClass2 extends BaseClass {
authorName: string;
}
Child Class 3:
import { BaseClass } from './base-class';
export interface ChildClass3 extends BaseClass {
operationType: string;
}
In my component, how can I achieve generalisation just like we have in Java.
For ex: Have an object named,
public models: BaseClass[] = [];
How can I have different types of objects like ChildClass1
, ChildClass2
, ChildClass3
present in the models objects above.
EDIT:
The following is a screenshot that shows the error in Visual Studio Code.
Upvotes: 0
Views: 600
Reputation: 328362
Yes, you can do this. The problem you are probably having is that when you use a "fresh" object literal (that is, one that hasn't already been assigned to some variable) and assign it to a variable of a specified type, TypeScript uses excess property checks to warn you if you are adding unexpected properties. Types in TypeScript are normally treated as open/extendable/generalizable, but in this one instance they are treated as closed/exact, because it's often an error to do this.
const excessPropertyErrors: BaseClass[] = [
{ id: "a", entityId: "b", entityName: "c", commentId: "d", authorName: "e" },
{ id: "f", entityId: "g", entityName: "h", authorName: "i" },
{ id: "j", entityId: "k", entityName: "l", operationType: "m" }
]; // error! excess property checks are biting us
So, how can we prevent this from happening to us? All sorts of ways, in fact.
One is to explicitly mention the subclasses we expect. The type BaseClass
and BaseClass | ChildClass1 | ChildClass2 | ChildClass3
are structurally identical, but they differ by how they see "extra" properties:
const explicitlyMentionSubclasses:
Array<BaseClass | ChildClass1 | ChildClass2 | ChildClass3> = [
{ id: "a", entityId: "b", entityName: "c", commentId: "d", authorName: "e" },
{ id: "f", entityId: "g", entityName: "h", authorName: "i" },
{ id: "j", entityId: "k", entityName: "l", operationType: "m" }
]; // okay
That would still throw an excess property error if you misspell operationType
, so you might want to mention the types in some way. Still, having the type of your variable carry around the subclasses is kind of limiting.
Another way to do this is to assign the fresh object literal to variables annotated with the specific subclass type you care about, and then put those variables in your array:
const childClass1: ChildClass1 =
{ id: "a", entityId: "b", entityName: "c", commentId: "d", authorName: "e" };
const childClass2: ChildClass2 =
{ id: "f", entityId: "g", entityName: "h", authorName: "i" };
const childClass3: ChildClass3 =
{ id: "j", entityId: "k", entityName: "l", operationType: "m" };
const individualElements: BaseClass[] = [childClass1, childClass2, childClass3]; // okay
That's one of the safest ways you can do this, since each variable childClass1
, childClass2
and childClass3
will be constrained and excess-property-checked to the relevant subclass.
Another way you can do this is to completely turn off excess property checks by having the object literals no longer be "fresh". First, assign the array to an unannotated variable; the type will be inferred by the compiler. Then, assign the inferred-type variable to a BaseClass[]
variable:
const completelyInferred = [
{ id: "a", entityId: "b", entityName: "c", commentId: "d", authorName: "e" },
{ id: "f", entityId: "g", entityName: "h", authorName: "i" },
{ id: "j", entityId: "k", entityName: "l", operationType: "m" }
];
const noLongerFresh: BaseClass[] = twoStepProcess1; // okay
That works, but keep in mind that there will be absolutely no checking of anything except the BaseClass
members. If you make operationType
a number, it will still be accepted.
You could decide that you want BaseClass
never to have excess property checks ever. One way to turn that completely off is to define a BaseClass
extension which explicitly allows any excess properties of any type:
interface BaseClassWithUnknownExtraProperties extends BaseClass {
[k: string]: unknown; // add index signature
}
const indexSignature: BaseClassWithUnknownExtraProperties[] = [
{ id: "a", entityId: "b", entityName: "c", commentId: "d", authorName: "e" },
{ id: "f", entityId: "g", entityName: "h", authorName: "i" },
{ id: "j", entityId: "k", entityName: "l", operationType: "m" }
]; // okay
Finally, you might want to use a helper function to accept extensions of BaseClass
(which turns off excess property checking):
const asBaseClassArray = <T extends BaseClass>(arr: T[]) => arr;
const usingHelperFunction: BaseClass[] = asBaseClassArray([
{ id: "a", entityId: "b", entityName: "c", commentId: "d", authorName: "e" },
{ id: "f", entityId: "g", entityName: "h", authorName: "i" },
{ id: "j", entityId: "k", entityName: "l", operationType: "m" }
]); // okay
Hope one of those ways gives you some direction. Good luck!
Upvotes: 4