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, 2023In 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: string4 constructor(name) {5 this.name = name6 }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}1213const 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.
Modifier | Class | Inheriting classes | Other code |
---|---|---|---|
Public | Accessible | Accessible | Accessible |
Protected | Accessible | Accessible | Not accessible |
Private | Accessible | Not accessible | Not 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: string5 ) {}6 public info(this: Computer) {7 console.log(`Name of the computer: ${this.name}`)8 }9}1011class Desktop extends Computer {12 private gpu: string13 constructor(name: string, id: string, gpu: string) {14 super(name, id)15 this.gpu = gpu16 }17}1819class Laptop extends Computer {20 private display: string21 constructor(name: string, id: string, display: string) {22 super(name, id)23 this.display = display24 }25}2627const 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}45console.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(): void3}45class Desktop extends Computer {6 constructor(private gpu: string) {7 super()8 }9 info(): void {10 console.log(`Desktop gpu: ${this.gpu}`)11 }12}1314class Laptop extends Computer {15 constructor(private display: string) {16 super()17 }18 info(): void {19 console.log(`Laptop display: ${this.display}`)20 }21}2223const desktop = new Desktop('AMD Radeon Pro W6800X')24const laptop = new Laptop('16"')2526desktop.info() // Desktop GPU: AMD Radeon Pro W6800X27laptop.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: string3 readonly id: string4 info(): void5}67// You can replace an interface with a type.8type ComputerType = {9 name: string10 readonly id: string11 info(): void12}1314let desktop: Computer15desktop = {16 name: 'Mac Studio',17 id: 'gdd89s',18 info() {19 console.log(`Name of the computer: ${this.name}`)20 }21}2223desktop.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: string3}45interface Informed {6 info(): void7}89class Computer implements Named, Informed {10 name: string11 constructor(name: string) {12 this.name = name13 }14 info(): void {15 console.log(`Name of the computer: ${this.name}`)16 }17}1819const 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) => void23interface InfoInterface {4 (name: string): void5}67const 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.