Builder pattern using TypeScript interfaces

Viewed 14712

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};
3 Answers

The following design adds type-safety by accomplishing 3 things:

  1. It is aware which of the required properties have been already provided.
  2. It is aware which of the optional properties have been already provided.
  3. Will only let you 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

Based on the previous answers I wrote a generic Typescript builder which provides:

  • Type-Safety
  • Differentiation of optional and required properties
  • Generic method (with) for adding parts of the object
  • Validation of the object before creating it
  • Domain Driven Design conformity (no need to specify methods when using Builder)

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

Related