Welcome! Today we’re going to be brushing up on TypeScript classes, interfaces, inheritance, and other object-oriented programming (OOP) concepts.
While TypeScript and JavaScript might not be the first languages you think of when it comes to object-oriented programming, you’d be surprised to know just how much support there is for building out complex, robust components in an object-oriented style. The latest versions of TypeScript and JavaScript both introduced changes to their syntax to make it easier for OOP to take place.
By popular demand, we’ll be focusing specifically on OOP concepts in TypeScript today. We’ll start with a basic rundown of what classes are, and then move on to concepts like encapsulation, class inheritance, interfaces, and more!
By the end, you should have a basic understanding of how to implement various OOP concepts in TypeScript.
Try one of our 300+ courses and learning paths: TypeScript for Programmers.
Before the release of ECMAScript 6 (ES6), JavaScript was primarily a functional programming language where inheritance was prototype-based. When the syntax for supporting classes was introduced in 2015, TypeScript quickly adapted to take advantage of object-oriented techniques like encapsulation and abstraction.
TypeScript and JavaScript classes are comprised of the following:
Classes provide a fundamental structure for creating reusable components in JavaScript. They are an abstraction in object-oriented programming (OOP) languages used to define objects, and pass down properties and functions to other classes and objects.
Objects are data structures made by encapsulating data, and the methods that work on that data. So, you can think of JavaScript classes as not being objects in the truest sense, but more similar to a blueprint for objects.
Note: TypeScript fully supports the class syntax that was introduced in 2015 with the release of ECMAScript 6 (ES6).
Class declarations are used to define a class using the class keyword along with the class name, and curly braces ‘{}’.
Class expressions are another way to define a class, but they can be named or unnamed.
You can access named class expressions using the name keyword.
A key concept of object-oriented programming is encapsulation. Encapsulation entails restricting access to an object’s state by enclosing data and methods into one unit. Restricting access to certain data or components can be useful for preventing outside code from calling private methods within a specific class.
TypeScript facilitates encapsulation by enclosing data and its related methods within a class.
For example, if you have a ‘class Student’, with two data elements, and a method, you can encapsulate them using the following syntax:
Objects are also variables, but instead of a single value, they can be assigned multiple values written as key : value pairs. The collection of these key : value pairs make up different properties of the object they belong to.
To access class members (data or methods) we have to create an instance of their object.
In the following example, there are two classes, Student, and School. Using the object of class School, we can try to access a data member of the ‘Student’ class with uni.roll.
Doing so will return a warning that the desired data does not exist in class School. Instead, we can fix the code by commenting uni.roll and uncommenting student.roll.
Try it out yourself!
A class can have a special method or function known as a ‘constructor’ that gets called automatically when we create an object of that class. A constructor can be used to initialize the class data or perform other actions upon instantiation of its objects. However, it’s not necessary for a class to include a constructor.
Below, the example demonstrates a class ‘Car’ with a public constructor that is automatically called upon object instantiation. At the same time, the constructor creates an instance of the class.
Another key concept of object-oriented programming that TypeScript supports is inheritance. Inheritance allows us to derive a class from another (parent or super) class, thus extending the parent class’s functionality. The newly created classes are referred to as child or sub classes.
Child classes inherit all properties and methods from their parent class, but do not inherit any private data members or constructors.
You can use the extends keyword to derive child classes from parent classes.
class child_class extends parent_class
There are three types of class inheritance:
An access modifier restricts the visibility of class data and methods.
TypeScript provides three key access modifiers:
publicprivateprotectedAll class members in TypeScript are public by default, but can otherwise be made public using the public keyword. These members can be accessed anywhere without restriction when no modifier is specified.
A public method or data of a class can be accessed by the class itself or by any other (derived or not) class.
In the example above, ‘fruit_plu’ and ‘fruit_name’ keywords are both considered to be public class members even though ‘fruit_plu’ is the only one with an access modifier in front of it. Remember, when you don’t specify the scope of a class member, it can be accessed from outside of the class.
A private method or data member of a class cannot be accessed from outside of its class. You can use the private keyword to set its scope. When the scope of a private member is limited to its class, only other methods in that same class can have access to it.
In the example above, attempting to access the fruit_plu member will return a compiler error because its scope has been set to private. You will still be able to access the fruit_name member because its scope is by default, public.
A protected access modifier works similarly to the private access modifier, with one major exception. The protected methods or data members of a class can be accessed by the class itself and also by any child class derived from it. With TypeScript, you can use the constructor keyword to declare a public property and a protected property in the same class. These are parameter properties and can allow you to declare a constructor parameter and a class member at the same time.
Note: One caveat of TypeScript’s type system is that the private and protected scopes are only enforced during runtime type checking.
Now that you have a basic understanding of inheritance and access modifiers, we can demonstrate how these two concepts can be used to modify access to some members of a class but not others.
In the following example, you’ll see that the scope of all class members is readily accessible. You should be able to execute this program without returning any errors.
However, we have also included code that alters the scope of different class members as comments. Try uncommenting different sections of the code to see how access is enforced by different access modifiers! You’ll also get to see what kind of errors are returned by TypeScript.
One of the most common mistakes in object-oriented programming is unintentionally shadowing a parent class method with the wrong signature. TypeScript 4.3 introduced the override keyword to help catch these errors at compile time.
class Animal {speak(): void {console.log("Generic sound");}}class Dog extends Animal {override speak(): void {console.log("Woof!");}}
If you misspell speak as speek, the compiler will now throw an error. This small keyword makes inheritance safer and your code more reliable.
There are two ways to define types for your data in TypeScript: type aliases and interfaces.
Type aliases are declared using the type keyword, and are used to explicitly annotate a type with a name (alias). Type aliases can be used to represent primitive data types like string or boolean, but they can also be used to represent object types, tuples, and more!
Unlike interfaces, type aliases cannot be declared more than once, and cannot be changed after being created.
Note: Fun fact! The TypeScript compiler converts TypeScript entirely into JavaScript code during a process called transpilation. This is to ensure that any and all JavaScript programs are fully compatible with the TypeScript programming language.
Interfaces are another way to define the data structure of your objects. It is an abstract type that tells the TypeScript compiler which properties a given object can have. In TypeScript, interfaces provides the syntax for an object to declare properties, methods, and events, but it’s up to the deriving class to define those members. Unlike type aliases, you can freely add new fields to an existing interface.
You can declare an interface using the interface keyword. In this example, we can define an interface for an apple with the properties ‘variety’ and ‘color’ as strings.
To implement the Apple interface, we simply assign values to the properties.
Interfaces can be thought of as a blueprint for the data structure that deriving classes must follow. In the example below, we’ll look at how to implement an interface with a class using the implements keyword.
Just like with classes, we can derive an interface from other interfaces through inheritance. Unlike classes, a single interface may be extended from multiple interfaces.
In the example below, we demonstrate how the interface ITeacherAndStudent can be derived from the IStudent and ITeacher interfaces.
Try one of our 300+ courses and learning paths: TypeScript for Programmers.
Static blocks let you run initialization code for static properties in a single, controlled place; perfect for complex configurations.
class Config {static settings: Record<string, string>;static {Config.settings = {apiUrl: process.env.API_URL || "https://api.default.com",};console.log("Configuration initialized");}}
This feature helps keep class logic self-contained and reduces the need for external setup scripts.
Decorators are no longer just an experimental feature—they’re now part of the TypeScript standard (since 5.0). They’re widely used for logging, dependency injection, and attaching metadata to class members.
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {const original = descriptor.value;descriptor.value = function (...args: any[]) {console.log(`Calling ${propertyKey} with`, args);return original.apply(this, args);};}class Calculator {@Logadd(a: number, b: number) {return a + b;}}
Decorators make your classes more expressive and are especially powerful in frameworks like Angular, NestJS, and TypeORM.
Great job! By now, you should have a basic understanding of TypeScript classes, interfaces, and how to use them alongside other object-oriented programming concepts. That said, you don’t have to stop learning quite yet. TypeScript is a powerful tool for making JavaScript easier for collaborative efforts. There is a huge library of fantastic resources that are available for developers of all levels.
To help you master TypeScript, we’ve created the TypeScript for Programmers learning path to help you build advanced TypeScript programming skills.
Happy learning!