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.