Categories
JavaScript TypeScript

Multiple Inheritance with TypeScript Mixins

Spread the love

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.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *