TypeScript is a natural extension of JavaScript that’s used in many projects in place of JavaScript.
However, not everyone knows how it actually works.
In this article, we’ll look at how to work with classes and interfaces in TypeScript.
Class Inheritance
TypeScript builds on the standard clas inheritance features to make them more familiar to developers using other programming languages.
It keeps the extends keyword like JavaScript for creating subclasses.
For instance, we can write:
class Person {
constructor(public name: string) {}
}
class Student extends Person {
constructor(readonly id: number, public name: string) {
super(name);
}
read() {
//...
}
}
We have the Student subclass that has its own parameters in the constructor.
We use the super keyword to call the parent constructor.
Also, we have a read method that’s exclusive to the Student class.
Type Inference for Subclasses
The TypeScript compiler may not be able to infer types from classes.
It’s easy to produce unexpected results by assuming that it has insights into the structure of the created objects.
For instance, if we have:
class Person {
constructor(public name: string) {}
}
class Student extends Person {
constructor(readonly id: number, public name: string) {
super(name);
}
read() {
//...
}
}
const person = new Person("james");
const student = new Student(1, "james");
const people: (Person | Student)[] = [person, student];
people.forEach(p => {
if (p instanceof Person) {
console.log("Person");
} else {
console.log("Student");
}
});
We’ll see that both objects are considered to be an instance of Person.
So we get 'Person' logged for both objects.
Abstract Class
Abstract classes are classes that can’t be instantiated directly and are used to describe common functionality that must be implemented by subclasses.
It forces subclasses to adhere to a specific structure but allows for class-specific implementations of specific methods.
To define one, we can use the abstract keyword.
For instance, we can write:
abstract class Person {
constructor(public name: string) {}
getName(): string {
return this.name;
}
abstract getSpecificDetails(): string;
}
class Student extends Person {
constructor(public id: string, public name: string) {
super(name);
}
getSpecificDetails() {
return `${this.id} - ${this.name}`;
}
}
The code above has an abstract Person class with a getName method and a constructor.
Then we have the Student class that implements the Person class.
This is indicated with the extends keyword.
We implemented the getSpecificDetails and return a string.
Type Guarding an Abstract Class
Abstract classes are implemented as regular classes in JavaScript generated by the TypeScript compiler.
The TypeScript compiler prevents us from creating objects with abstract classes.
However, this isn’t carried over to the JavaScript code, which means that we may be able to create objects with classes that are declared abstract in our TypeScript code.
Using Interfaces
Interfaces are similar to object shape types.
A class can implement an interface, which means it must conform to the interface.
For instance, we can write:
interface Person {
name: string;
getName(): string;
}
class Employee implements Person {
constructor(public name: string) {}
getName() {
return this.name;
}
}
We have a Person interface that has the name and getName members.
Then since Employee implements the Person interface as indicated by the implements keyword, we’ve to include both members.
The interface keyword indicates that it’s an interface.
Merging Interface Declarations
We can merge multiple interfaces with the | to form a union or & to form an intersection.
It’s like unions or intersections with object shape types. Intersections mean all members of both interfaces must be included in our object.
Union means that any member of the interface can be included.
Implementing Multiple Interfaces
We can implement multiple interfaces in one class. Then we need to implement all the members of both interfaces.
For instance, we can write:
interface Person {
name: string;
getName(): string;
}
interface Owner {
ownedItems(): string[];
}
class Employee implements Person, Owner {
constructor(public name: string) {}
getName() {
return this.name;
}
ownedItems() {
return ["food"];
}
}
We have an Employee class that implements both Person and Owner interfaces.
It has all the members from both.
Conclusion
We can implement inheritance with TypeScript. It improves the class syntax’s inheritance syntax.
TypeScript has abstract classes and interfaces to enforce the implementation of classes.