Issue
In TypeScript it's possible to create a class with a constructor that takes parameter with access modifiers and it automatically convert those parameters in class fields.
class Item {
constructor(
public id: number,
public updatedAt: number,
public createdAt: number,
) {}
}
const item = new Item(1, 1, 1);
item.id // 1
I'm wondering if there is a way to pass all those parameters in an object instead
class Item {
constructor({
public id: number,
public updatedAt: number,
public createdAt: number,
}) {}
}
const item = new Item({ id: 1, updatedAt: 1, createdAt: 1 });
item.id // 1
Is this possible? Ever gonna be possible?
Are there workarounds to do something similar?
Solution
The simplest way would be to declare the fields in the class and use a mapped type as a parameter, then use Object.assign
to assign the fields to this
. We have several options for which mapped type to use:
Partial<T>
Type will contain all members (fields and methods) of the class but all of them are optional. The disadvantage here is we can't make some of the fields required, and the caller can potentially override a method
class Item {
public id: number;
public updatedAt: number;
public createdAt: number;
constructor(data: Partial<Item>) {
Object.assign(this, data);
}
method() {}
}
//Works
const item = new Item({ id: 1, updatedAt: 1, createdAt: 1 });
//This also works unfortunately
const item2 = new Item({ id: 1, method() { console.log('overriden from param !')} });
Pick<T, K>
This mapped type allows us to pick some properties from T
by specifying a union of several string literal types that are keys of T
. The advantages are that Pick
will inherit whether the field is required or not from the original declaration in the class (so some fields can be required and other optional) and since we specify which members we pick, we can omit methods. The disadvantage is that we have to write property names twice (once in the class and once in the Pick):
class Item {
public id: number;
public updatedAt?: number;
public createdAt?: number;
constructor(data: Pick<Item, "id" | "updatedAt" | "createdAt">) {
Object.assign(this, data);
}
method() {}
}
const item = new Item({ id: 1 }); //id is required others fields are not
const item2 = new Item({ id: 1, method() {} }); // error method is not allowed
Custom Mapped Type That Removes Methods
The third option would be create a type similar to Pick
that includes all class fields but not the methods automatically. We can do this in Typescript 2.8 using conditional types (unrelease at the time of writing, but should be release in March 2018, you can get it right now via npm install -g typescript@next
). This has the advantages of Pick
without the need to specify filed names again:
type NonMethodKeys<T> = {[P in keyof T]: T[P] extends Function ? never : P }[keyof T];
type RemoveMethods<T> = Pick<T, NonMethodKeys<T>>;
class Item {
public id!: number;
public updatedAt?: number;
public createdAt?: number;
constructor(data: RemoveMethods<Item>) { // No need to specify field names again
Object.assign(this, data);
}
method() {}
}
const item = new Item({ id: 1 }); //id is required others fields are not
const item2 = new Item({ id: 1, method() {} }); // error method is not allowed
Answered By - Titian Cernicova-Dragomir
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.