Reputation: 3731
I am just starting out with Typescript, and I cannot understand if it's possible to have a class property be an object, which would hold any arbitrary property in addition to properties declared in the class. For example, I defined here a name
as a property of a Person
, and then under properties
you should be able to define any other arbitrary characteristics for the person, such as their height. It seems that assigning it goes okay, but trying to access it on line 12 throws an error saying:
Property 'height' does not exist on type 'Object'
Fair enough! I know there is no guarantee that a property named height
would be under something that is just an object, but there should still be a way to do this.
Here is the code:
class Person {
public name: string;
public properties: Object;
constructor(name: string, other: Object) {
this.name = name;
this.properties = other;
}
}
let props: Object = { height: 200 };
var nick = new Person("Bob", props);
console.log(nick.properties.height);
And here is an alternative I've tried, which throws exactly the same error:
class Person {
public name: string;
public properties: Object;
constructor(name: string, other:{ height: number }) {
this.name = name;
this.properties = other;
}
}
var nick = new Person("Bob", { height: 200 });
console.log(nick.properties.height);
Another alternative with an interface I've just tried, that still doesn't work.
interface PersonProperties {
height: number;
}
class Person {
public name: string;
public properties: Object;
constructor(name: string, other: PersonProperties) {
this.name = name;
this.properties = other;
}
getHeight(): number {
return this.properties.height;
}
}
var properties: PersonProperties = { height: 200 };
var nick = new Person("Bob", properties);
document.write(nick.getHeight().toString());
Upvotes: 1
Views: 12683
Reputation: 31
You can use type mapping. Create type that consists of all keys (Wrap) and class which should map the properties you want to work with (UserIdentifier) and than mix that in parameter of your function (constructor of User object in my example)
type Wrap<T> = {
[P in keyof T]?: T[P]
}
type UserIdentifier = {
userId: string;
}
class User {
constructor(user: Wrap<any> & UserIdentifier) {
this.user = user;
}
user: Wrap<any> & UserIdentifier;
userId(): string {
return this.user.userId;
}
}
//then can be called as
let u = new User({userId:"usr1", someOther:1, andOther:2, andSoOn:3});
//or just
let u2 = new User({userId:"usr1"});
//however this will throw an error because required userId property is not present
let u3 = new User({someOther:1});
Upvotes: 0
Reputation: 96891
The error happens because you're defining public properties: Object;
and Object
really doesn't have a property height
. Even though you declare correct type in the constructor with { height: number }
property properties
is still expected to be an Object
.
You could do for example this:
type PropertiesObject = { height: number };
class Person {
public name: string;
public properties: PropertiesObject;
constructor(name: string, other: PropertiesObject) {
this.name = name;
this.properties = other;
}
}
let props = <PropertiesObject>{ height: 200 };
var nick = new Person("Bob", props);
console.log(nick.properties.height);
Using interface as you did is correct as well.
Note that you can always use good old square brackets notation. This should compile successfully even when using just Object
:
console.log(nick.properties['height']);
Upvotes: 2
Reputation: 13807
Since the static type of Person#properties
is simply Object
the type checker doesn't keep any additional type information about it, that's why you get the compile errors. You can solve this issue 2 ways
The "dumb" way with any
:
class Person {
constructor(public name: string, public other: any) {}
/* ... */
}
const p = new Person("doge", { wow : "such property" })
console.log(p.other.wow) // no error, also no type checking
any
in ts basically "disables" typechecking, and lets you access any properties on a variable typed as any
The slightly smarter way with generics
class Person<PropType> {
constructor(public name: string, public other: PropType) {}
}
const p = new Person("doge", { wow : "such property" })
console.log(p.other.wow) // ok
console.log(p.other.amaze) // error
This way each person instance will have an associated properties type, so it's checked "compile" time if the property you trying to access is known by the compiler.
I'd recommend some reading on generics if this doesn't look familiar from other languages: https://www.typescriptlang.org/docs/handbook/generics.html
Upvotes: 6