TypeScript 4.3 — I object, your honour!

Eamonn Boyle
Level Up Coding
Published in
6 min readMay 26, 2021

--

In celebration of TypeScript 4.3, I take a look TypeScript’s Object Oriented features and look at the new features this release brings.

TypeScript provides good support for both object-oriented programming and function-oriented programming (I’m not saying functional programming as I don’t want the purists to hunt me down).

In this post, I’m going to review OO support within TypeScript and show the latest OO features provided in TypeScript 4.3.

I’ll quickly cover:

  • Basic Class Mechanics & the Prototype-based System
  • Accessibility and Parameter Properties
  • Inheritance, Types and Type Assertions
  • Accessors and 4.3’s support for differing types
  • ECMAScript Private and 4.3’s extension to methods and accessors
  • 4.3’s new override keyword

TypeScript OO Review

Classes, Objects & Prototypes, oh my!

Object-Oriented programming in JavaScript has always been a little bit strange and with TypeScript being a superset of JavaScript, some of that funk is still hanging in the air.

The prototype system for managing classes can feel strange when coming from other object-oriented languages like Java, C# or C++. In JavaScript, a class actually creates an object in memory, just like any other object, to contain common functionality that all instances of that class share (typically methods). This object is the prototype, accessed via the prototype property from the class and the __proto__ property from all instances.

When we instantiate an object of a class, we actually create a new blank object, but that blank object has a prototype which points to the shared instance so inherits its behaviour. The constructor function runs which will typically populate the blank object with properties.

In the olden days, this was more visible where we’d actually write classes as functions and manipulate the prototype for the function/class.

Of course, since ECMAScript 2015 (ES6) we have proper class syntax:

and this syntax carries over to TypeScript:

But realise that the underlying behaviour is the same, with constructor functions and prototype objects.

Accessibility, Properties & Parameter Properties

Notice that in our Circle class in TypeScript we had to define a radius property. This property can be configured to be private, protected or public (with public being the default). These accessibility modifiers also apply to methods.

In the constructor, as is often the case, we initialise the radius property with a parameter passed into the constructor. This can be simplified by adding an accessibility modifier to the constructer parameter, making it a parameter property. This removes the need for the property declaration and initialisation.

We can also qualify properties as readonly. This also works to create parameter properties.

Finally, we can also initialise properties outside of the constructor. Even using other properties and methods.

Inheritance

Prototype chains support OO inheritance which is easily implemented using extends.

What’s in a Type

A key concept to remember with classes in TypeScript is that an object is an instance of a class because of its prototype.

However, when we deserialise from JSON we are recreating objects that have no link to any classes we’ve written. They will not appear as instances of the class that was used to originally create them before serialisation and they won’t have any of the methods.

This is true even when we include a type assertion. Realise that type assertions are not casts; we are only making an assertion. We are informing the compiler about something that it is not capable of inferring for itself. This is usually at an I/O boundary or when calling into untyped JavaScript. However, it is possible for us to give incorrect information — such as assert that a basic object is an instance of a class.

This is why it is often better when transferring data to use interfaces with no methods. You may find with TypeScript that you tend towards a more function-oriented approach for this reason, using basic objects adhering to an interface and free functions that take these objects as inputs.

The Latest Features

Accessors with Conversion

Accessors are useful for providing an interface which is consumed like a property but with more complicated logic under the hood. Using get and/or set, we can bind a property to a getter and/or setter functions. This is useful for example when creating computed properties.

Here, diameter looks and feels like a property to the consumer, but in reality it is calling functions that read and write radius.

TypeScript 4.3 adds support for having a setter type that is more than the getter type. This can be useful for supporting writing multiple data types which will ultimately be converted to a canonical type. For example, below, we can write diameter as a number or string, but we always read it back as a number.

While you will not want to do this everywhere, it is useful for wrapping existing APIs with similar behaviour. In 4.3 we also get support for specifying separate getter and setter types in an interface.

The only restriction is that the getter type must be assignable to the setter type.

True Private Members

It is worth noting, that like many things in TypeScript, private and protected modifiers only apply at compile time. They are there to help and protect the developer. At runtime, when everything has been converted to JavaScript, all properties will exist in the object and are accessible. This includes when we serialise out objects to JSON.

Serialises out as:

{
"key": "123",
"id": "abc"
}

To provide true privacy, TypeScript added support for ECMAScript Private fields in version 3.8. This is only a stage 3 proposal (at the time of writing) for JavaScript but is part of the TypeScript language since 3.8. These fields are prefixed with a #.

This stores the value for the fields/properties external to the object so when we serialise the private fields are not present. So, an instance of this Secrets class serialises out as:

{}

The compilation of this to older JavaScript is interesting — it creates a module level WeakMap for each field and an object has to look up its field value in this structure, using itself as the key.

TypeScript 4.3 extends ECMAScript private support to include methods and accessors.

Clarity in the Override

Prior to TypeScript 4.3, when overriding methods in a subclass, you simply used the same name. This could lead to subtle errors when the base class changed but the subclasses weren’t updated. For example, when a base class method was removed.

TypeScript 4.3 introduces the override keyword to explicitly mark methods as overridden. If there is not a suitable base entry to override then an error is flagged. This also better communicates intent to the reader.

To avoid breaking-changes a new compiler switch, --noImplicitOverride, flags up errors when a method overrides a base method without this keyword.

Conclusion

I hope this was a useful summary of Object-Oriented support in TypeScript. As you can see, TypeScript 4.3 adds some useful features for this paradigm.

If you haven’t already done so, check out my posts showing some of the useful features in TypeScript 4.1 and TypeScript 4.2.

Also, be sure to check out our TypeScript course. We’re also happy to deliver Angular & React training using TypeScript. We deliver virtually to companies all over the world and are happy to customise our courses to tailor to your team’s level and specific needs. Come and check us out to see if we can help you and your team.

Originally posted here.

--

--

15 years working as a developer. For the last 5 years I've been working as a trainer and coach, authoring and delivering courses on a range of topics.