Lets say I have two interfaces which have two the same members id, and name:
export interface InterfaceA {
id: number;
name: string;
//some other members
}
export interface InterfaceB {
id: number;
name: string;
//some other members
}
I would like to get collection of elements of both types to populate some combobox. I need id, name and type of every element, so I've made following class
export class AssignableDevice {
id: number;
name: string;
type: string;
constructor(device: InterfaceA | InterfaceB) {
this.id = device.id;
this.name = device.name;
this.type = typeof device; //still returns "object"
}
}
// in onInit method :
ngOnInit() {
super.ngOnInit();
this.dataService.getInterfaceA().subscribe((data) => {
data.forEach((element) => this.devices.push(new AssignableDevice(element as InterfaceA)));
});
this.dataService.getInterfaceB().subscribe((data) => {
data.forEach((element) => this.devices.push(new AssignableDevice(element as InterfaceB)));
})
}
But problem is I always get "object" in "AssignableDevice" class constructor, and I have no idea why is this happening. I can achieve my goal by using some enum, but I wonder, why this solution is not working, and how could achieve this. I'd rather not make any changes in InterfaceA or InterfaceB.
You can't access the TypeScript type of an object at runtime (in the general case). TypeScript provides a compile-time type system. The typeof
that you're using is the JavaScript runtime typeof
, which always returns "object"
for objects of any kind (and for null
).
You've said you want to send the type to the backend, so you definitely need it at runtime. I can see at least two ways to do that:
You could define your interfaces as branded interfaces to ensure that you always include the type:
export interface InterfaceA {
id: number;
name: string;
//some other members
type: "InterfaceA"; // <== This is a _string literal type_ whose only valid value is the string "InterfaceA"
}
export interface InterfaceB {
id: number;
name: string;
//some other members
type: "InterfaceB"; // <=== String literal type
}
Now any object you assign to a variable, property, or parameter of type InterfaceA
has to have a type
property with the string "InterfaceA"
and similar for InterfaceB
. Then your code would use that type
property.
You could make your constructor private, and only allow creation via createX
methods for the interfaces:
export class AssignableDevice {
id: number;
name: string;
type: string;
private constructor(device: InterfaceA | InterfaceB, type: string) {
this.id = device.id;
this.name = device.name;
this.type = type;
}
static createA(device: InterfaceA): AssignableDevice {
return new AssignableDevice(device, "InterfaceA");
}
static createB(device: InterfaceB): AssignableDevice {
return new AssignableDevice(device, "InterfaceB");
}
}
Now you use the appropriate createX
method for the type of the object you have. Since you make that choice when writing the code, TypeScript can typecheck to see that you're passing the right type of object to createX
.