To simplify the process, one approach is to define the fields within the class and utilize a mapped type as a parameter. Subsequently, employ Object.assign
to assign these fields to this
. There are various options available for selecting the appropriate mapped type:
Partial<T>
This type encompasses all members (fields and methods) of the class, with each being optional. The drawback here is that it does not allow for setting certain fields as required, potentially enabling the caller to 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>
With this mapped type, specific properties can be selected from T
by specifying a union of string literal types that serve as keys in T
. The advantage lies in inheriting whether a field is required or optional from the original declaration in the class via Pick
, allowing for some fields to be mandatory while others remain optional. However, duplication of property names is required (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
A third option involves creating a type similar to Pick
which includes all class fields but excludes methods automatically. This can be achieved using conditional types in Typescript 2.8 (currently unreleased as of writing). This approach offers the benefits of Pick
without the need to specify field 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
Playground Link