TypeScript Mix — Yet Another Mixin Library

So I wrote another mixin library in typescript… What else is new? 😏

You can find the library here on github and on npm.

This article shall focus on my journey building a mixin library for TypeScript. The goal is to share my experience and insight authoring and using mixins with the TypeScript Mix Library and who knows, maybe we can all learn something.


Why Mixins?

Mixins offer a very easy yet powerful way to reuse code in a client. (i.e. the class using the mixins) It is sometimes referred to as multiple inheritance, but this definition can sometimes be misleading — even though mixins essentially have the same effect as multiple inheritance, it is cannot really be classified as inheritance.

Inheritance serves as a means by which we can define relationship between two classes. (the is-a relationship) The code sharing ability we get is just a consequence of that relationship, not the primary focus.

But a lot of times inheritance is often abused and we end up creating multiple chains of parent classes just so we can share code.

This article is not about the dangers of inheritance, but knowing when and when not to use inheritance will help appreciate the concepts I’m about to talk about. If you want to know more about how inheritance can be dangerous and abused, please read this article: Inheritance is Bad: Code Reuse

The article details how developers who solely rely on inheritance as a meas of code reuse end up creating overly complex and redundant code structures. At a point, they ended up with an inheritance tree that looked like this.

It is an unfortunate consequence of writing programs solely relying on inheritance. This is why inheritance should only be used when we want to define relationship not when we just want to reuse code. So if we define two classes A and B, if we cannot say B is an A then we should never write class B extends A {}.

Programming to an Interface

Interface first approach offers a new paradigm where we think about the behaviors first and implementation later. In the above table parent classes like Bird, Mammal, Animal, Fish are redundant since all they do is hold common implementation for their sub classes, with an interface first approach we could get rid of all those redundant classes and concentrate on what we need.

These has a lot of advantages classes can implement multiple interfaces. We no longer have to bother ourselves defining and redefining class hierarchies just to share code, we just implement them.

Only if it were that easy.

We might have solved a lot of the problems that come with inheritance by using interfaces, but our code is no longer DRY. We are implementing the fly method twice in the Parrot and Dove class so how could this be better?

Different languages have come up with interesting solutions for this problem. In Swift you can define extension methods that provide concrete implementations for this interface and every class implementing the interface gets those implementations like magic. Java has the interfaces with default method implementations, so you can actually define implementation in your interface and just like Swift, every class implementing that interface gets the default implementation.

Both extension methods and default implementations for interfaces are not available yet in TypeScript. But one pattern available in almost every OOP language is Composition. With Composition our code could look like this.

Now we’ve managed to remove duplicate implementations and our code is DRY-er.

Composition most of the time is enough as it’s easy to grasp with limited magic but also comes at a cost of too much boiler plate code. This might be difficult to see in this example, but as we implement more interfaces with more methods it becomes a pain writing this over and over again.

Mixins to the Rescue

Most experienced programmers tend to shy away from the Mixin Pattern and sincerely their concerns are valid.

Consider this mixin pattern in PHP

It is obvious $kangaroo calling the jump() method is going to fail and the fact that this is valid PHP code makes it more dangerous this one of the tend to shy away from mixins in general. See this stackoverflow thread on traits in PHP.

Lets assume for a second that mixins were safe to use. How would we implement them in TypeScript.


Mixins in TypeScript

The power of mixins have always been in their ability to give add-on functionality without the boilerplate. So its no surprise there have been attempts to bring this feature to TypeScript.

Before deciding to roll out my own custom library I came across a whole host of solutions and libraries for providing mixin functionality in TypeScript. Some of the notable solutions were:

A lot of these solutions while fantastic, wasn't enough for me. It was either too verbose or too complicated. As far as a mixin library was concerned, I wanted something:

  • Expressive and felt natural and intuitive to “use
  • That provided intellisense support (cuz what’s the point of using TypeScript if you’re not going to get intellisense.)

After using the applyMixin function for quite sometime, I decided to write my own custom library — TypeScript Mix and for a while I was satisfied.

You would simply declare a mixin like so:

And then use it in a client like so: 😄

😡 😡 Well that’s not what we wanted... But this highlights a major pain point of using mixins and something we experienced while using traits in PHP.


The Problem

If we think about how mixins work, they essentially copy/reference implementation (and sometimes state) from the mixin to the client (the class using the mixin) and there lies the problem.

In classical OOP, implementation is the responsibility of the class, while state is the responsibility of the object (or instance of the class) this means the Person class delegates the responsibility of assigning a name value to the boy object.

So how come this is not a problem in inheritance?

In inheritance (in most languages) you have a one-to-one relationship between a parent class and a subclass, but with mixins, you get a one-to-many relationship this is the tricky part.

Inheritance solves it’s problem by calling a super(…) method in the constructor and that works great. But how do you do that with mixins?

The Solution

We ignore state entirely, mix-in only the implementations, and make it the responsibility of the client to define/redefine state.

A particular advantage of using typescript-mix is that mixins are just regular classes or typed object literals, but classes usually come with constructors which usually come with properties that must be initialized, usually at runtime.

Since we now know it is the responsibility of the client to redefine state, we end up with something like this:

Notice how by adding the name:string property to the Person class, the TypeScript compiler forces us to initialize name either in the declaration or in the constructor.

Now if we call boy.shop() we get no errors. 😆

This is great! we’re making progress but now we have to always keep track of state in our mixins just to initialize them in our class.

If we think about it, that isn’t really a problem. When we extend a class, we always have to call super(…) in the constructor and if we forget the compiler complains and we go and fix our error. Also when class A implements interface B with methods b1() and b2() defined in the interface, the compiler complains until we fix our error by providing implementation for those methods.

So could this be a tooling problem?

With our mixins the problem is not that we have to redefine state, the problem is that the compiler doesn't care whether we redefine state or not.

So what if we could provide a fairly similar support, just like inheritance for mixins and we didn’t have to keep track of state in our mixins. Suddenly using mixins will be so much easier, but in order to do this we must have well defined standards that guide the use of mixins.


Defining Standards for the TypeScript Mix Library

Standards give us a safety net — an assurance that what we expect is what we get at all times. To ensure this, I had to set some ground rules.

  • Rule 1: State is always the responsibility of the client, whether or not such state has been initialized in the mixin.
  • Rule 2: All mixins must implement an interface.
  • Rule 3: All mixins must be self contained.

Done.

These rules actually make using mixins a breeze. We get the code reliability just like when we used inheritance while still getting all the functionality that comes with mixins. Armed with these two rules, all that was left was to figure out a way to enforce them.


TypeScript Mix Linter: A linter for the Typescript Mix Library

Linters are a great way of ensuring standards and best practices. So what if we could design a linter that would understand how mixins work in typescript-mix and sniff out code smells and potential bugs. That is exactly what tsmix-linter does.

A demo would be nice.

Consider this piece of code below:

This obviously a disaster waiting to happen and depending on your tsconfig.json compiler settings. TypeScript won’t even allow you do some of the things expressed here. But as we’ve explained earlier TypeScript has no knowledge of how our decorator works hence subtle bugs can still slip by.

So how can linting help us? Let’s start by installing tsmix-linter

$ npm install -g tsmix-linter

Then we can run:

$ tsmix-linter "test.ts"

And we get

Whaaattt!!… How cool is that?

It gets better

Having a linting server is really cool, we could even watch the file and lint as we make changes to our file. But wouldn't it be cooler to have that linting experience in our IDE?

VSCode — TypeScript Mix Linter

vscode-tsmix-linter is a simple extension library written for that exact reason.

If installed, we get an experience that looks like this:

This is a big upgrade from how we used mixins earlier — where our code was prone to bugs and errors. Now its all about making sure the red squiggly lines go away and you can be sure of bug free code. Also notice how it plays well with the default TypeScript compiler.

The linter also never assumes too much and also never lints outside the client where the decorator is used — So you can be rest assured it won’t get in your way.

Its Really Just Composition without the Boilerplate.

There’s actually very little difference between the Composition Pattern explained above and the mixin pattern using the use decorator. We've just gotten rid of the additional boiler plate code.

Mixins are Testable

Probably one of the biggest advantages of this pattern, is that it forces us to create self dependent mixins that don’t rely on any external factors — and since mixins are just classes or typed object literals, they can be easily instantiated and tested.


There’s More…

TypeScript Mix 3.0 comes with a new decorator called delegate and some breaking changes to typescript-mix 1.0.0 and 2.0.0. see repo.

Often times we want to share implementation with two more classes that are not in any way related to each other, but we don’t want to mix-in all the methods or inherit functionality, we just want to express that class A shares two methods a1() and a2() from class B.

I know what you’re thinking. This is very easy to do. Infact this is one of the areas JavaScript does so well, so we could just write

class A {
  ...
}
class B {
...
}
A.prototype.b1 = B.prototype.b1
A.prototype.b2 = B.prototype.b2

And we’re done.

In TypeScript it’s a bit more complicated.

Consider this answer by one of TypeScript’s creators.

Here MyClass needs an method1 from OtherClass.

But we’re not doing anything differently from the previous example, the only difference here is you have to redefine the method signature in the MyClass interface, so TypeScript’s compiler knows what you’re doing.

This can start getting messy especially when you’re sharing more than 3 methods from different sources. One thing I’ve always loved about using classes is that everything I need to know about how my class operates is defined in the class body. With the delegate decorator we can have all that.

Here’s what it would look like.

Just like the case of mixins with the use decorator, we get both intellisense support and linting support.

Note: In order to use this library, you have to set strictPropertyInitialization option to false in your tsconfig compiler options.

Shutting up the Linter

Just like regular TypeScript code, there are simply times when you need to tell the linter “shut up” and go along with your business. Thankfully, just like TypeScript, tsmix-linter also supports the “///@ts-ignore” flag. Just add to the preceding line and you’re done.


This was a really long article, but I hope it gets you excited on the possibilities of TypeScript Mix Linter and TypeScript in general.

So would you use TypeScript Mix?

Tell me what you think 👇