The builder pattern in TypeScript is amazing. However, the way you use it in TypeScript is completely different to other languages. Typically, builders are used to add support for optional and default parameters in languages that don’t support them directly. TypeScript already supports optional and default parameters though - so what’s the point?
We can actually use builders as a workaround for other issues in the type system. In TypeScript, that means a way to enforce complex constraints like only allow sending an event if anyone is listening for that event
. In this (long but beginner-friendly) post, we do an in-depth walkthrough of how to create a TypeScript builder class to enforce a complex constraint like that one.
We start by introducing a simple data processing task and discuss how it would be useful to create a generic version. That immediately gets too hard, so we introduce builders to save the day. Step-by-step, we analyse the problem and design a builder to solve the problem. We then discuss the pros and cons of that approach, and explore whether it was even necessary in the first place.
Let’s imagine a basic data processing task:
const input = "524";
const a = input.split("").reverse().join("");
const b = parseInt(input, 10);
const c = b * 5;
The rest of this blog post is dedicated to over-engineering that tiny bit of code. It’s clearly overkill in this case, but that’s inevitable when we use a simple example to demonstrate an advanced technique.
We saw how to do that task as a one-off, but what if we wanted it to be a configurable reusable function? We can define a function that takes a few config parameters:
function process(input: string, radix: number, multiplicand: number) {
const a = input.split("").reverse().join("");
const b = parseInt(input, radix);
const c = b * multiplicand;
return c;
}
process("524", 10, 5);
Often, it’s easier to take the config as a single object:
function process(input: string, config: {radix: number, multiplicand: number}) {
const a = input.split("").reverse().join("");
const b = parseInt(input, config.radix);
const c = b * config.multiplicand;
return c;
}
process("524", {radix: 10, multiplicand: 5});
That’s useful as it allows the config to be loaded from a JSON file. Conveniently it also makes our job easier later on. What are the odds?
#swaterman #tech #typescript