March 30, 2023 7 min read

Object-Oriented Programming in TypeScript

Object-oriented programming is a foundation for many programming languages. So, we'll familiarize ourselves with the OOP TypeScript syntax and compare it to JavaScript.

Last updated: March 30, 2023
Many white pillars in two rows
Photo by Chris Brignola

In the last post, I presented the rationale behind TypeScript. I briefly described it and listed basic types helpful during day-to-day software development. But basic types are not enough for building modern, complex web apps.

In this post, I assume you understand OOP in vanilla JavaScript. You don't know about it? No worries, I got you covered - Object-Oriented Programming in JavaScript.

To model real-world things, we need objects. And classes. And interfaces. In this post, we'll cover all of that. At the end of this post, we should have a good understanding of OOP in TypeScript. We should know all the syntax and how it compares to the OOP in JavaScript.

Class

Defining classes in TypeScript is identical to the vanilla language (at least to ECMAScript 6). There is the class keyword you can use for this purpose. However, when it comes to class fields, we have more options. TypeScript offers modifiers that change the visibility of particular properties.

ClassesTYPESCRIPT
1class Computer {
2 // There are no private or public keywords in vanilla JavaScript.
3 private name: string
4 constructor(name) {
5 this.name = name
6 }
7 // The public modifier is the default, but you can set it explicitly.
8 public info(this: Computer) {
9 console.log(`Name of the computer: ${this.name}`)
10 }
11}
12
13const desktop = new Computer('Mac Studio')
14const laptop = new Computer('Macbook Pro')

Did you know that private class features are available in native JavaScript too? ECMAScript 2022 introduced private instance fields, methods, and accessors. Precede the property name with the hash (#) symbol to use this new syntax. TypeScript 3.8 (and the above versions) also supports this new JavaScript syntax for private fields.

The property is written down twice in the above snippet. TypeScript offers a shortcut to avoid such duplication.

Shortcut for defining propertiesTYPESCRIPT
1class Computer {
2 constructor(private name: string) {}
3 // It's a hint to Typescript regarding what "this" should be referred to.
4 public info(this: Computer) {
5 console.log(`Name of the computer: ${this.name}`)
6 }
7}

Inheritance

If you know how inheritance works in JavaScript, TypeScript won't surprise you (okay, maybe a little). Classes inherit from each other via the extends keyword. There is also a modifier connected to the inheritance - protected. Fields marked as protected are accessible within the class or any class that extends it. The below table compares TypeScript field modifiers.

ModifierClassInheriting classesOther code
PublicAccessibleAccessibleAccessible
ProtectedAccessibleAccessibleNot accessible
PrivateAccessibleNot accessibleNot accessible

Besides mentioned modifiers, there is another way of protecting fields in TypeScript. The readonly keyword does what the name suggests - it marks properties as read-only. You can't change them after initialization.

Classes and inheritanceTYPESCRIPT
1class Computer {
2 constructor(
3 protected name: string,
4 protected readonly id: string
5 ) {}
6 public info(this: Computer) {
7 console.log(`Name of the computer: ${this.name}`)
8 }
9}
10
11class Desktop extends Computer {
12 private gpu: string
13 constructor(name: string, id: string, gpu: string) {
14 super(name, id)
15 this.gpu = gpu
16 }
17}
18
19class Laptop extends Computer {
20 private display: string
21 constructor(name: string, id: string, display: string) {
22 super(name, id)
23 this.display = display
24 }
25}
26
27const desktop = new Desktop('Mac Studio', 'sdag89', 'AMD Radeon Pro W6800X')
28const laptop = new Laptop('Macbook Air', 'bzkx32', '13.6"')

Static Members

We've been talking about instance methods and properties so far. But there is also a way to access properties and methods directly on the class. The static keyword defines a static method or field for a class. It works both in JavaScript and TypeScript. Most likely, you've already been using static methods/properties. All properties and methods of the Math object - like Math.PI or Math.abs() - are static.

Static membersTYPESCRIPT
1class Computer {
2 static firstProgrammer = 'Ada Lovelace'
3}
4
5console.log(Computer.firstProgrammer) // Ada Lovelace

Abstract Class

Sometimes you want to be sure that a class has implemented a specific method. Classes inherit methods, but you can't provide a default implementation. You want to enforce developers working with a class to create their version. That's where abstract classes are handy. TypeScript offers the abstract keyword you can use with classes and methods.

Abstract classesTYPESCRIPT
1abstract class Computer {
2 abstract info(): void
3}
4
5class Desktop extends Computer {
6 constructor(private gpu: string) {
7 super()
8 }
9 info(): void {
10 console.log(`Desktop gpu: ${this.gpu}`)
11 }
12}
13
14class Laptop extends Computer {
15 constructor(private display: string) {
16 super()
17 }
18 info(): void {
19 console.log(`Laptop display: ${this.display}`)
20 }
21}
22
23const desktop = new Desktop('AMD Radeon Pro W6800X')
24const laptop = new Laptop('16"')
25
26desktop.info() // Desktop GPU: AMD Radeon Pro W6800X
27laptop.info() // Laptop display: 16"

Interface

Simply put, an interface describes the structure of an object. In this regard, it's similar to an abstract class but not the same. An abstract class can also define an implementation. An interface defines only the structure. It's not a blueprint like a class. It's more like a custom type. You can use interfaces and custom types interchangeably, but they're not identical. Interfaces are for objects only. Custom types are more flexible and can store other things like union types. So what's the point of an interface? It's clearer. You can be sure that you work with an object using an interface.

Interfaces and typesTYPESCRIPT
1interface Computer {
2 name: string
3 readonly id: string
4 info(): void
5}
6
7// You can replace an interface with a type.
8type ComputerType = {
9 name: string
10 readonly id: string
11 info(): void
12}
13
14let desktop: Computer
15desktop = {
16 name: 'Mac Studio',
17 id: 'gdd89s',
18 info() {
19 console.log(`Name of the computer: ${this.name}`)
20 }
21}
22
23desktop.info()

A class can implement an interface and use it as a contract. There is the implements keyword for this purpose. Unlike inheritance, a class can implement multiple interfaces. Interfaces can also extend themselves. It gives you a lot of flexibility regarding code reusability and composability.

Interfaces and classesTYPESCRIPT
1interface Named {
2 name: string
3}
4
5interface Informed {
6 info(): void
7}
8
9class Computer implements Named, Informed {
10 name: string
11 constructor(name: string) {
12 this.name = name
13 }
14 info(): void {
15 console.log(`Name of the computer: ${this.name}`)
16 }
17}
18
19const laptop = new Computer('MacBook Pro')
20laptop.info()

You can also use interfaces to define the structure of a function. "Whoah, whoah, haven't you just mentioned that they are only for objects?" Yes, and there is no contradiction because functions are first-class objects in JavaScript. Yeah, JS has its mysteries. Anyway, you can use an interface as a replacement for function types. It's a less common syntax, but you have this alternative.

Interfaces and functionsTYPESCRIPT
1type Info = (name: string) => void
2
3interface InfoInterface {
4 (name: string): void
5}
6
7const info: InfoInterface = (name: string) => {
8 console.log(`Name of the computer: ${name}`)
9}

I've spent some time describing interfaces, but at the end of the day (or compilation, I should say), the interfaces are not present in the JavaScript code. It's a pure development feature brought by TypeScript.

Interfaces after compilationJAVASCRIPT
1var info = function (name) {
2 console.log('Name of the computer: '.concat(name))
3}

Just like interfaces vanish after compilation, I will vanish after describing them. I want those TypeScript posts to be easily digestible, so I'll end here. Of course, there are more topics when it comes to TypeScript. But I'll touch on the more complex ones, like generics or decorators, in the following posts. If you want to read about less complex concepts, check my last post - TypeScript basic types.

A newsletter that sparks curiosity💡

Subscribe to my newsletter and get a monthly dose of:

  • Front-end, web development, and design news, examples, inspiration
  • Science theories and skepticism
  • My favorite resources, ideas, tools, and other interesting links
I am not a Nigerian prince to offer you opportunities. I do not send spam. Unsubscribe anytime.

Stay curious. Read more

Half of a record on white backgroundSeptember 1, 20227 min read

Accessible animations in React

Or how not to spin your users round (like a record). Some animations can make users sick. We'll take care of them and make non-essential animations optional.

Read post
List of CSS variables in Visual Studio Code.September 14, 20228 min read

Converting design tokens to CSS variables with Node.js

Converting design tokens is an error-prone process - I found about it the hard way. So, I made a simple Node.js script that will help me with that task.

Read post
Five metal gears on a black brackgroundSeptember 23, 202211 min read

Gatsby with Netlify CMS

In this post, we will look closely at a Netlify CMS. It is an example of a new type of CMS that is git-based. We will integrate it with a Gatsby example project.

Read post