In this article you will be introduced to the concept of mixins in TypeScript with simple and clear terms, so even if you're new to the concept you can follow along and get motivated enough to adopt this tool into your workflow.
TypeScript
TypeScript is an open-source, strongly-typed, object-oriented compiled language developed and maintained by Microsoft. It is a superset of the very popular JavaScript used by millions of projects (over 2 million on GitHub) that was built to bring static types to modern JavaScript. The TypeScript compiler reads in TypeScript code, which has things like type declarations and type annotations and emits a clean and readable JavaScript with those constructs transformed and removed.
What You Will Learn
This article is an introduction to the concept and use of mixins in TypeScript and why they are important. It will cover the syntax, the types and how the parameters are built, all with clearly defined examples that you can follow up yourself in your integrated development environment of choice.
Prerequisites
To follow through properly and learn from this article, you will need to have the following things ready on your computer:
- Installation of Node: you can check if you have Node installed already by running the command below:
node -v
- Installation of Node package manager: Node usually comes with a required version of npm.
- Installation of TypeScript: If you have the Node package manager installed, you can use it to install TypeScript globally on your machine with this command below:
npm install -g TypeScript
- Integrated Development Environment (IDE): In this article, you are going to be making use of Visual Studio Code from the team at Microsoft. You can download it here. Head over to the file location where you downloaded it and install it by following the prompt and remember to click the “Add open with code” option so you can easily access VS Code from any folder in your machine.
- Create a new folder in a location of your choice and then open it up in your IDE.
- Create a file called test.ts inside this new folder where you will test out all the examples in the tutorial yourself as you go through it.
This article is written for TypeScript developers at any level of knowledge—this includes but is not totally focused on beginners. The steps to set up a working environment are, however, added for the benefit of developers new to TypeScript and the Visual Studio Code application.
Extending Classes in TypeScript
Just like in many object-oriented programming languages, TypeScript has classes. Classes are blueprints for creating objects—they are used to basically encapsulate data found in objects. A TypeScript class can be defined like this below:
class Subscribe {
Remind(){
console.log('Remember to subscribe to my channel');
}
}
This class houses a function called remind which logs a string in the console of the DOM. If you have a new class, say YouTube:
class Youtube {
Begin(){
console.log('Hi, Welcome to my channel!');
}
Ending(){
console.log('Thank you for watching my video!');
}
}
And if you wanted to have a class that extends the two classes we already defined to have access to them in this new class, TypeScript does not allow that so it is flagged as an error. You can try it like this:
export class Recording extends Youtube, Subscribe{}
The Problem
If you tried this, you would see squeaky lines that, when you hover over your IDE, would tell you TypeScript classes can only extend a single class. This problem, when you encounter it on a first look, you might remember interfaces.
Interfaces
In TypeScript, classes can only extend a single class but an interface can extend more than one class. Using a TypeScript interface to solve our problem:
export class Recording {}
export interface Recording extends Youtube, Subscribe{}
const recording = new Recording
recording.Remind();
recording.Ending();
We create an interface and then try to see if we can access the functions inside the two classes we already defined. If you run build in your terminal:
tsc filename.ts
node filename.js
You will see an error in the terminal for the build saying the remind and the ending are not functions. This is because TypeScript interfaces, even though they can extend classes, do not have the class implementations in them. This brings us back to the initial problem where we cannot access more than one class in a new class in TypeScript.
Solution: Mixins
Mixins are a way to implement reusing components in the object oriented programming paradigm. So for our problem space, we can use mixins with just one helper function to specify the behavior we wanted, which is to extend two classes in a new class in TypeScript. So you might be familiar with mixins if you are a frontend developer and use SASS. It is the same concept, but for extending classes. The helper function can be found on the official documentation of TypeScript here.
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name));
});
});
}
You can now use the ApplyMixins function to describe what exactly you want, so for our ts file it would all come together to look like this:
class Youtube {
Begin(){
console.log('Hi, Welcome to my channel!');
}
Ending(){
console.log('Thank you for watching my video!');
}
}
class Subscribe {
Remind(){
console.log('Remember to subscribe to my channel');
}
}
// export class Recording extends Youtube, Subscribe{}
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name));
});
});
}
export class Recording {}
export interface Recording extends Youtube, Subscribe{}
applyMixins(Recording,[Youtube, Subscribe]);
const recording = new Recording
recording.Remind();
recording.Ending();
After bringing in the helper function, it does the mixing for us by running through the properties of each of the mixins and copying them to the target of the mixins, filling out the stand-in properties with their implementations. The ApplyMixins function now helps you specify the exact interface to look at and carry out the extension from.
If you rerun the build code again, you should see that the terminal has now successfully printed “Remember to subscribe to my channel” from the remind function in the subscribe class and “Thank you for watching my video” in the ending function inside the YouTube class, just as we wanted.
Conclusion
This has been an introductory post on mixins in TypeScript, how they help with the implementation of extended classes and how they can be used in your workflow. Stay safe and happy hacking!