Emmanuel John I'm a full-stack software developer, mentor, and writer. I am an open source enthusiast. In my spare time, I enjoy watching sci-fi movies and cheering for Arsenal FC.

TypeScript mixins: Examples and use cases

3 min read 855

TypeScript Mixins

One of the challenges I’ve experienced with TypeScript is the limit on only inheriting or extending from a single class at a time. You can circumvent this constraint, especially in a more complex architecture, by using TypeScript mixins to improve multiple class inheritance.

In this tutorial, we’ll learn more about mixins in general, explore TypeScript mixins, and walk through a typical mixins use case.

Here’s what we’ll cover:

To follow along with this tutorial, you should have:

I used playcode.io to write the example programs.

What are mixins?

Mixins are special classes that contain a combination of methods that can be used by other classes. Mixins promote code reusability and help you avoid limitations associated with multiple inheritance.

Even though attributes and instantiation parameters are defined at compile time, mixins can defer definition and binding of methods until runtime.

Creating mixins with TypeScript

To create a mixin, we’ll take advantage of two aspects of TypeScript: interface class extension and declaration merging.

Interface class extension is used, unsurprisingly, to extend multiple classes in TypeScript. Declaration merging refers to TypeScript’s process of merging together two or more declarations with the same name. Interfaces can also be merged into classes and other constructs if they have the same name.

Here’s an example of declaration merging:

    interface Car {
      steering: number;
      tyre: number;
    }
    interface Car {
      exhaustOutlet: number;
    }
    // contains properties from both Car interfaces
    const BMW: Car = {
        steering: 1,
        tyre: 4,
        exhaustOutlet: 2
    };

Now that we understand these two TypeScript features, we’re ready to get started.

First, we need to create a base class to which the mixins will be applied:

class Block {
    name = "";
    length = 0;
    breadth = 0;
    height = 0;
    constructor(name: string, length: number, breadth: number, height: number, ) {
        this.name = name;
        this.length = length;
        this.breadth = breadth;
        this.height = height;
    }
}

Next, create the the classes to which the base class will extend:

class Moulder {
  moulding = true;
  done = false
  mould() {
    this.moulding = false;
    this.done = true;
  }
}
class Stacker {
  stacking = true;
  done = false
  stack() {
    this.stacking = false;
    this.done = true;
  }
}

Create an interface that merges the expected classes with the same name as your base class (Block):

interface Block extends Moulder, Stacker {}

The new interface is defined with the exact same name as the class Block we created earlier. This is crucial because this interface is extending both Moulder and Stacker classes. This means the interfaces will merge their method definition into a single construct (the interface) while at the same time merging into the class definition with the same name.

Due to declaration merging, the Block class will be merged with the Block interface.

Create a function

To create a function to join two or more class declarations, we’ll use the function provided in the official TypeScript handbook:

function applyMixins(derivedCtor: any, constructors: any[]) {
  constructors.forEach((baseCtor) => {
    Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
      Object.defineProperty(
        derivedCtor.prototype,
        name,
        Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
          Object.create(null)
      );
    });
  });
}

The preceding function iterates over Moulder and Stacker classes, then iterates over its list of properties and defines those properties into the Block class. Essentially, we’re manually linking all methods and properties from the Moulder and Stacker classes into the Block class.

To continue, execute the preceding function as follows, then check out the example below:

applyMixins(Block, [Moulder, Stacker]);

TypeScript Mixin example

let cube = new Block("cube", 4, 4, 4);
cube.mould();
cube.stack();
console.log(cube.length, cube.breadth, cube.height, cube.name, cube.moulding, cube.stacking);

Here, we assigned cube to the instance of the base class Block. Now the Block instance can directly access the mould() and stack() methods from the Moulder and Stacker classes, respectively.

While there are other ways to create TypeScript Mixins, this is the most optimized pattern because it relies less on the compiler and more on your codebase to ensure both runtime and type-system are kept in sync.



Common use cases for TypeScript mixins

Let’s go over some use cases for TypeScript mixins you’re likely to encounter or may want to consider.

Handling multiple class extension

TypeScript classes cannot extend several classes at the same time unless a mixin is introduced to the interface.

Consider the following snippet:

class Moulder {
  moulding = true;
  done = false
  mould() {
    this.moulding = false;
    this.done = true;
  }
}
class Stacker {
  stacking = true;
  done = false
  stack() {
    this.stacking = false;
    this.done = true;
  }
}
class Block extends Moulder, Stacker{
 constructor() {
    super()
 }
}

In this example, the Block class tries to extend two classes at the same time without introducing the mixins concept. If you add this snippet to the online editor (playcode.io), you’ll get the following error:

TypeScript Multiple Class Extension Error
TypeScript multiple class extension error

At this point, the only solution to this limitation is to introduce TypeScript mixins.

Conclusion

TypeScript mixins come in handy when building applications that are likely to grow in complexity. When building TypeScript applications with complex architecture, you’ll want to extend multiple classes at the same time. With mixins you can overcome the limitations associated with multiple inheritance.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

.
Emmanuel John I'm a full-stack software developer, mentor, and writer. I am an open source enthusiast. In my spare time, I enjoy watching sci-fi movies and cheering for Arsenal FC.

Leave a Reply