In JavaScript, there’s no easy way to inherit from multiple classes. We can make our own mixins to inherit from multiple objects. This is made easier in TypeScript by making mixins a standard. With TypeScript, we can make interfaces that extend multiple classes or interfaces. This way, we can reuse multiple partial classes to create a new child class. We do this with mixins and copy over the properties to a new class that derive members from parent classes with our own function.
To define a interfaces that inherit from multiple classes in TypeScript, we create an interface that extends multiple classes or interfaces. Each of these classes or interfaces is called a mixin. We can mix and match them to create a combined interface to have whatever properties that we want. For example, we can write something like the following code:
class Animal {
species: string;
constructor(species: string) {
this.species = species
}
}
class Person {
name: string;
constructor(name: string) {
this.name = name
}
}
interface Employee extends Person, Animal {
employeeCode: string;
}
let employee: Employee = {
species: 'human',
name: 'Joe',
employeeCode: '123'
}
In the code above, we made an Employee
interface which inherits all the members from the Animal
and Person
classes and we incorporated the employeeCode
member into the Employee
interface. This lets us create an Employee
object with the species
, name
, and employeeCode
properties.
We can only use the extends
keyword with multiple classes or interfaces if we use the keyword in the interfaces. It won’t work if we use it in a class. For example, if we write:
class Animal {
species: string;
constructor(species: string) {
this.species = species
}
}
class Person {
name: string;
constructor(name: string) {
this.name = name
}
}
class Employee extends Person, Animal {
employeeCode: string;
}
let employee: Employee = {
species: 'human',
name: 'Joe',
employeeCode: '123'
}
Then we get the error message:
Classes can only extend a single class.(1174)
We can also inherit from interfaces, along with classes in an interface like we do in the code below:
class Animal {
species: string;
constructor(species: string) {
this.species = species
}
}
class Person {
name: string;
constructor(name: string) {
this.name = name
}
}
interface Job {
title: string;
}
interface Employee extends Person, Animal, Job {
employeeCode: string;
}
let employee: Employee = {
species: 'human',
name: 'Joe',
employeeCode: '123',
title: 'laborer'
}
As we can see, interfaces are very flexible, we can inherit from different interfaces and classes whatever way we want, unlike classes. Each of our classes is called a mixin.
If we have overlapping properties in our mixins, then they’ll be combined together with declaration merging operations done by TypeScript. For example, if we have 2 classes with the some overlapping members like we have in the code below as long as the overlapping members are identical:
class Animal {
species: string;
id: number = 0;
constructor(species: string) {
this.species = species
}
}
class Person {
name: string;
id: number = 0;
constructor(name: string) {
this.name = name
}
}
interface Job {
title: string;
}
interface Employee extends Person, Animal, Job {
employeeCode: string;
}
let employee: Employee = {
id: 1,
species: 'human',
name: 'Joe',
employeeCode: '123',
title: 'laborer'
}
As we can see, we have an id
member in both the Person
and Animal
interfaces and they’re both of the number
type. Overlapping members with the same name and type are allowed for multiple inheritance. However, overlapping members with the same name but different types aren’t allowed. For example, if we have:
class Animal {
species: string;
id: string = '';
constructor(species: string) {
this.species = species
}
}
class Person {
name: string;
id: number = 0;
constructor(name: string) {
this.name = name
}
}
interface Job {
title: string;
}
interface Employee extends Person, Animal, Job {
employeeCode: string;
}
let employee: Employee = {
id: 1,
species: 'human',
name: 'Joe',
employeeCode: '123',
title: 'laborer'
}
Then we get the error message:
Interface 'Employee' cannot simultaneously extend types 'Person' and 'Animal'.Named property 'id' of types 'Person' and 'Animal' are not identical.(2320)
since we id
in the Animal
class is a string
, but id
in the Person
class is a number.
Copying Implementation Parent Classes to the Derived Class
To copy the mixin methods into a new class. We can write the following function to copy the methods from the parent class into a new class. The function has to loop through the base classes and get the content inside the classes and then define new properties in the class derived from the parent classes and then set them one by one in the new class. This is works because classes are just syntactic sugar for constructor objects that are in JavaScript since the early days.
We can write the following function to achieve what we want:
function applyMixins(derivedConstructor: any, baseConstructors: any[]) {
baseConstructors.forEach(baseConstructor => {
Object.getOwnPropertyNames(baseConstructor.prototype)
.forEach(name => {
Object.defineProperty(derivedConstructor.prototype,
name,
Object.
getOwnPropertyDescriptor(
baseConstructor.prototype,
name
)
);
});
});
}
The applyMixin
does exactly what we described above. Now we just have to call it with the child class that inherits the parent classes as the first argument and then an array with the parent classes as the second argument. We call it as in the following code:
applyMixins(Employee, [Person, Animal])
Then given that we have the following classes defined:
class Animal {
species: string;
id: number = 0;
constructor(species: string) {
this.species = species
}
eat(){}
}
class Person {
name: string;
id: number = 0;
constructor(name: string) {
this.name = name
}
speak(){}
}
interface Employee extends Person, Animal {
employeeCode: string;
}
class Employee {
}
We should get that the prototype
of Employee
having the eat
and speak
methods. We can call eat
directly on a Employee
object like the following code:
let employee: Employee = new Employee();
employee.eat();
We can define our mixins with our class notation to let us do multiple inheritance with TypeScript. Then we define an interface that specifies which mixins we inherit from. Once we did that, we copy over the members that are in the parent classes to the child class’ prototype. Then we have all the properties of the parent classes accessible from the child class.