TypeScript - Mixins



TypeScript is an Object-oriented programming language and contains the classes, which is a blueprint for the object. The class can be defined as shown below in TypeScript.

class MathOps {
    // defining a method
    add(a: number, b: number): void {
        console.log('sum is: ', a + b);
    }
}

Now, suppose we have multiple classes like the above which contain different operations.

What if you want to reuse both classes and want to create a third class by extending both classes? For example, if you try to extend the 'allOps' class with 'MathOps1', and 'BitwiseOps' classes, TypeScript will give you an error as multiple inheritance is not allowed in TypeScript.

class allOps extends MathOps, BitwiseOps {
    // Executable code
}

To solve the above problem, developers can use the mixins in TypeScript.

Introduction to Mixins

In TypeScript, mixins is a concept that allows us to extend a single class via multiple classes. This way, we can reuse the class components and combine their methods and properties in a single class.

We can use the declaration merging technique to extend the single class via multiple classes.

Declaration Merging

When you have two declarations of the same name, it will be merged without throwing any error.

For example, in the below code, we have defined the interface 'A' twice containing different properties. After that, we have created the 'obj' object of type 'A', which contains the properties 'a' and 'b' as both interfaces 'A' are merged.

// Definition of an interface with the same name twice
interface A {
    a: string;
}

interface A {
    b: string;
}

// Object that implements the interface
let obj: A = {
    a: 'a',
    b: 'b'
};

console.log(obj.a); // a
console.log(obj.b); // b

On compiling, it will generate the following TypeScript code.

// Object that implements the interface
let obj = {
    a: 'a',
    b: 'b'
};
console.log(obj.a); // a
console.log(obj.b); // b

Output

The output of the above example is as follows –

a
b

Now, let's understand how we can use the declaration merging technique to extend multiple classes with a single class.

Implementing Our Mixin Helper Function

Let's understand the below example code line-by-line.

  • We have defined the 'swimmer' class, which contains the StartSwim() and EndSwim() methods.

  • Next, we have defined the Cyclist class, which contains startCycle() and endCycle() methods.

  • Next, the 'combineMixins()' function is a helper function that allows us to mix the properties and methods of two or more classes in one class. That's why it is called mixin function.

    • The function takes the derived or parent class as a first parameter, and the array of base or child classes as a second parameter.

    • It iterates through the array of base classes using the forEach() method.

    • In the forEach() method callback, it iterates through each property of the single base class and adds in the prototype of the derived class using the defineProperty() method.

  • After that, we have defined the 'Biathlete' class.

  • The interface 'Biathlete' extends the 'Swimmer' and 'Cyclist' classes to merge all property and method declarations of both classes into the 'Biathlete' class. However, it won't combine the implementation of methods.

  • Next, we call the 'combineMixins()' function which merges the implementations of methods of the classes in other classes.

  • Next, we created the instance of the 'Biathlete' class and used it to call the methods of the 'Swimmer' and 'Cyclist' classes.

// Swimmer class definition
class Swimmer {
    // Methods
    StartSwim() {
        console.log('Starting the swimming session...');
    }
    EndSwim() {
        console.log('Completed the swimming session.');
    }
}

//   Cyclist class definition
class Cyclist {
    // Methods
    StartCycle() {
        console.log('Starting the cycling session...');
    }
    EndCycle() {
        console.log('Completed the cycling session.');
    }
}

// export class Biathlete extends Swimmer, Cyclist{}
function combineMixins(derived: any, bases: any[]) {
    // Iterate over the base classes
    bases.forEach(base => {
        // Iterate over the properties of the base class
        Object.getOwnPropertyNames(base.prototype).forEach(name => {
            // Copy the properties of the base class to the derived class
            Object.defineProperty(derived.prototype, name, Object.getOwnPropertyDescriptor(base.prototype, name));
        });
    });
}

// Export Biathlete class
export class Biathlete { }
// Use interface to combine mixins
export interface Biathlete extends Swimmer, Cyclist { }
// Combine mixins
combineMixins(Biathlete, [Swimmer, Cyclist]);

// Create an instance of Biathlete class
const athlete = new Biathlete();
// Call the methods
athlete.StartSwim();
athlete.EndSwim();
athlete.StartCycle();
athlete.EndCycle();

Output

Starting the swimming session...
Completed the swimming session.
Starting the cycling session...
Completed the cycling session.

This way, we can merge the structure of two components using the interface. After that, we can use the mixins function to combine the implementations of two components into the third one and reuse them.

Advertisements