Using a Golang pattern to write better Typescript

Gabe Szczepanek
The Startup
Published in
3 min readJun 22, 2020

How the Functional Options pattern can be used to construct better class definitions

The functional options pattern is a well established one in the Golang community and one I was particularly impressed with when learning the language. It’s clever yet simple in its implementation and straightforward in its benefits.

So what is the functional options pattern really and how can we use it to construct classes in Typescript? Let’s explore some common ground first and examine how current patterns for class construction fall a little short in some areas.

Below is the typical pattern for constructing a class object with each property being set by an individual constructor argument.

const myHouse = new House(5, 2, 'wood')

This pattern is fine for simple objects but quickly breaks down if:

  • Your class contains many properties
  • Your class has a mixture of optional & required properties
  • You need to implement validation on property values being set

Any of the above cases will cause bloating of your constructor whether by the amount of arguments it takes or by the added logic to check arguments, set defaults and perform validation. The result will be messy.

As you can see the number of arguments is exhausting, as well as the need to add ‘if’ statements to validate every argument passed in. And this does not even address if multiple arguments are optional and the setting of defaults!

The “Options” or “Config” object

This is probably the most common pattern used to combat the issues described above. It consists of typing and passing in a single options object to the constructor which can contain all the object properties as, well, properties on the config object. This does a nice job of reducing the constructor arguments down to one, and solves the issue of having a combination of optional and required properties as that can be typed accordingly in the definition of the config object.

const myHouse = new House({
floors: 2,
rooms: 5,
material: 'wood',
})

But this pattern does not really address the awkward-ness of setting default values, nor the need for constructor-bloating ‘if’-statements in order to validate property values being set. Additionally, the repetitiveness of defining both the class and the typically-mirrored options interface is less than ideal.

As you can see in the above sample, this can get out of hand quickly.

Typescript Functional Options

Below you will find an implementation of the functional options pattern using static class methods as the option constructors.

const myHouse = new House(
House.WithRooms(5),
House.WithFloors(2),
House.WithMaterial('wood')
)

This pattern is definitely more verbose when it comes to implementation, however it has several advantages over the previously described patterns:

  1. The constructor definition itself is very straightforward. Its arguments definition size is small and does not grow with the amount of class properties added.
  2. The setting of default values is straightforward, with no need for ‘if’-statements checking the optionality of passed in arguments. Property defaults are set immediately on the object, with changing those properties from their defaults being inherently optional for the caller.
  3. Validation of each property is contained in its own context instead of bloating the constructor with ‘if’-statements. An added bonus is that, inherent in this pattern, validation only runs if the property is being set to something other than its default value.

The astute reader will also notice that you could take advantage of the options constructors if you are inclined to write class setter functions, which could certainly save you some time.

class House {  ...  public setRooms(roomCount: number): void {    House.WithRooms(roomCount)(this)
}
}

Conclusion

The Functional Options pattern arose in Golang largely due to a combination of specific features of the language itself, and the need for more flexible object construction as a result of those language features. The pattern, however, is not specific to Golang and clearly has benefits for object construction in other languages as well.

Using the functional options pattern for Typescript class definitions can help “future-proof” your classes by making them clearer and easier to work with, if and when they become more complex over time.

This pattern can also help you write async class constructors:

Happy Coding!

--

--