I’ve been writing Typescript for ~7 months now, and it has been fantastic. One of the things I love the most about TS is that it’s so easy to get into; there is almost no barrier to entry. If you know JS, you know TS — just add some type annotations and start enjoying the great features this language has to offer.
However, one drawback of this approach is that many TS developers (me included) don’t ever get to know the more advanced features and capabilities of the language, and there’s a lot of cool stuff we’re missing. In this post, I hope to give a few tips on how to write nice, clean classes using TypeScript.
No need to know any TS to enjoy this post, as it’s pretty easy to pick up the gist from the code samples. You do need to understand, at least, what classes and instances are.
Plain ES6 Class
We can start with a plain ES6 class, and build our TS code on top:
This is nice, but we don’t get much privacy. We managed to prevent giving our user an invalid name while creating the instance, but after that, we don’t have any control over what happens.
Adding Some Type Safety
Let’s add some TypeScript to our example and see what we get:
Keeping Our Properties Private
That’s better! Now we can at least know that name is a string and not an array or something else… but name is still a public property of
User. In fact, all properties in TypeScript classes are public by default. Let’s change that:
Good! Now we know
name is private and cannot be accessed outside our class. In addition to
private, we can set class properties to be
public (the default behavior) or
protected (which will only be accessible from subclasses of this class).
Get Rid Of Some Boilerplate Code
Note that in the last snippet, we first declared
name as a private property of type
string (line 2), then we declared it as an argument to our constructor (line 4), and finally, we assign the argument as a property (line 6). That’s a bit verbose, and we can have shorter and cleaner code by using a nice feature of TS called Parameter properties. In short, parameter properties are syntactic sugar that allows us to squeeze the three declarations we mentioned above into one line.
By now, we have a nice TypeScript class with type-safe and concise code. We no longer have to worry about coders passing arrays instead of strings to our constructor, or damaging our internal implementation. Now we can add some more functionality and explore some more cool features that TS has to offer. But first, let’s take a look at the whole thing:
Getters and Setters
Now, let’s add a way to get and set the property
name. The simplest way of achieving this would be to create two new methods:
setName(name: string). That would work just fine, but we might want to provide a nicer API. Sometimes, it just feels more intuitive to write
user.name = "Groot" instead of
user.setName("Groot"). Luckily for us, TypeScript allows us to do exactly that using the
get keywords. Here is what it will look like:
Note that we changed our internal property name from
_name. That’s because using
set creates a new property with the name of the methods we defined (in this case —
name). Now users of our class can freely access the “public” property
name, while our date is safely stored in the private property
Properties of the Class Itself
So far, we have only worked with properties and methods that belong to the instance, meaning the object that is created by the constructor. TypeScript allows us also to define properties and methods that belong to the class itself, by using the keyword
static. Static properties can be accessed by prepending the name of the class to the name of the property.
In our example, the method
assertValidName doesn’t access any of the instance data, so we can define it as a static method. I will use that opportunity to extract the minimum number for name length to a static property, so we’ll get a more readable code.
This Will Never Change
We know that some of the properties we define will never change. Sometimes, we even rely on the assumption that a particular property will always stay the same once we set it. If, for example, we identify users by email, then we don’t want other coders working on this class to change this field. It might be a good idea to mark this property as
readonly, so the TypeScript compiler won’t allow any changes to the property
Using the parameter properties shortcut, it would be straightforward to add an
minimumNameLength a read-only member.
What Have We Accomplished
Ok! We’ve made some progress with our class. Here is what we have up to this point:
Compared to the plain JS class we started with, this code is a lot safer, more readable and more expressive! Our intentions with this code are crystal clear to the reader and expressed by the code itself without using any comments. Even if we put this work down for a year or so, it would be easy to come back to it and know exactly what we intended in every line. This is the very definition of clean code.
There is still much more to TypeScript classes. We haven’t talked about inheritance and interfaces at all! But don’t worry, we’ll keep those until next time. If you feel you can’t wait for my next post, and you want to deepen your TS understanding, go and read the TypeScript handbook — I highly recommend it.
If you liked this post, feel free to show your love by smashing that little clap button for a few seconds, and check out my next post regarding the use of generics and type-augmentations!