Programming

TypeScript Mixins Part One

For the most up-to-date information on TypeScript mixins, view TypeScript Mixins Part Two! Before you start looking for Part Two, I’d like to declare that it doesn’t exist – and may never exist. The reason I’ve named this Part One is because I think that the Mixins feature in TypeScript will mature and grow and this article will eventually be out of date.

For now, though, you can get access to mixins in TypeScript and there are only three things you need to know in order to use them.

The Mixin Function

The mixin function does all the heavy lifting. This is where the behaviours from the mixin classes get augmented into a class representing the combined behaviours.

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    });
}

You need this function somewhere in your program that can be accessed by all the mixins – you only to include it once. You could wrap it within a module or a class if you wanted to.

(This is one part that I think will change – I can see this being generated by the compiler when it detects mixin usage in your code).

Declaring Mixins

Mixins just look like tiny classes in TypeScript. Each mixin should do one very specific thing. You can name them to make their behaviour obvious:

class Flies {
    fly() {
        alert('Is it a bird? Is it a plane?');
    }
}

class Climbs {
    climb() {
        alert('My spider-sense is tingling.');
    }
}

class Bulletproof {
    deflect() {
        alert('My wings are a shield of steel.');
    }
}

Augmenting Mixins

Now you can create smoosh-ins based on the available list of mixins by treating them like interfaces that come with an implementation. For example:

class BeetleGuy implements Climbs, Bulletproof {
        climb: () => void;
        deflect: () => void;
}
applyMixins (BeetleGuy, [Climbs, Bulletproof]);

class HorseflyWoman implements Climbs, Flies {
        climb: () => void;
        fly: () => void;
}
applyMixins (HorseflyWoman, [Climbs, Flies]);

The important bits of this example are:

  • You use the implements keyword, not the extends keyword.
  • You need to have a matching signature to keep the compiler quiet (but it doesn’t need any real implementation – it will get that from the mixin).
  • You need to call applyMixins with the correct arguments.

Calling applyMixins is another part that I think may eventually make its way into the compiler. Perhaps the compiler will detect you are using mixins based on the implements [Class], [Class] syntax. When it detects mixins it will ensure the function is available and called in the JavaScript output.

You can then use the augmented class. You get intellisense for all of the members and the implementation comes from the mixin, not the augmented class – so there is exactly one place in your program to change the “climb” behaviour.

var superHero = new HorseflyWoman();
superHero.climb();
superHero.fly();

Further Reading

There is more information on mixins, and the source code for the mixin function on Codeplex: Mixins in TypeScript.

Full Example

Here is the full code sample, you can paste it straight into the TypeScript Playground to try it out for yourself.

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
             if (name !== 'constructor') {
                derivedCtor.prototype[name] = baseCtor.prototype[name];
            }
        });
    });
}

class Flies {
        fly() {
                alert('Is it a bird? Is it a plane?');
        }
}

class Climbs {
        climb() {
                alert('My spider-sense is tingling.');
        }
}

class Bulletproof {
        deflect() {
                alert('My wings are a shield of steel.');
        }
}

class BeetleGuy implements Climbs, Bulletproof {
        climb: () => void;
        deflect: () => void;
}
applyMixins (BeetleGuy, [Climbs, Bulletproof]);

class HorseflyWoman implements Climbs, Flies {
        climb: () => void;
        fly: () => void;
}
applyMixins (HorseflyWoman, [Climbs, Flies]);

var superHero = new HorseflyWoman();
superHero.climb();
superHero.fly();