Reputation: 1152
I would like to do something like this:
interface IPoint {
x : number;
y : number;
z? : number;
}
const diag : IPoint = IPoint.x(1)
.y(2)
.build();
I realize I could implement this myself, but was wondering if there was an automatic way to do this? given TypeScript already knows the type information.
Edit: I am requesting this kind of syntax because I can do this currently.
const diag : IPoint = {x: 1, y: 1};
Upvotes: 12
Views: 19080
Reputation: 15589
This handles the type:
interface IPoint {
x: number;
y: number;
z?: number;
}
type IBuilder<T> = {
[k in keyof T]: (arg: T[k]) => IBuilder<T>
} & { build(): T }
let builder = {} as IBuilder<IPoint>
const diag = builder.x(1).y(2).z(undefined).build()
But I don't know how will you create the actual Builder
thou. :)
You can play around with it at the playground
EDIT: Vincent Peng has created a builder-pattern
npm package out of this (as mentioned in the comment). Go and give it some love!
Upvotes: 20
Reputation: 31
Based on the previous answers I wrote a generic Typescript builder which provides:
If you are interested, you can find further information and examples here: https://github.com/hanterlantant/ts-generic-builder And the npm package here: https://www.npmjs.com/package/ts-generic-builder
Upvotes: 3
Reputation: 25790
The following design adds type-safety by accomplishing 3 things:
build
once you have provided all required properties.The Point
itself:
interface Point {
x: number;
y: number;
z?: number;
}
class Point implements Point {
constructor(point: Point) {
Object.assign(this, point);
}
}
The Point
builder:
class PointBuilder implements Partial<Point> {
x?: number;
y?: number;
z?: number;
withX(value: number): this & Pick<Point, 'x'> {
return Object.assign(this, { x: value });
}
withY(value: number): this & Pick<Point, 'y'> {
return Object.assign(this, { y: value });
}
withZ(value: number): this & Required<Pick<Point, 'z'>> {
return Object.assign(this, { z: value });
}
build(this: Point) {
return new Point(this);
}
}
Usage:
/**
* The `z` property is optional.
*/
new PointBuilder()
.withX(1)
.withY(1)
.build();
/**
* The `.build()` method cannot be called — we are still missing `y`.
*/
new PointBuilder()
.withX(1)
.withZ(1);
/**
* The `z` property is correctly recognized as `number` (as opposed to `number | undefined`).
*/
new PointBuilder()
.withX(1)
.withZ(1)
.z
Upvotes: 26