Samples for blog post How to implement mixins in typescript
Full code available here as a standalone project: https://github.com/nch3v/typescript-mixins
Samples for blog post How to implement mixins in typescript
Full code available here as a standalone project: https://github.com/nch3v/typescript-mixins
/** | |
* Copy properties of source object to target object excluding constructor. | |
* If a property with the same exists on the target it is NOT overwritten. | |
* | |
* @param target | |
* @param source | |
*/ | |
function extend(target:any, source:any) { | |
Object.getOwnPropertyNames(source).forEach( name => { | |
if (name!=="constructor" && !target.hasOwnProperty(name)) { | |
Object.defineProperty(target, name, | |
Object.getOwnPropertyDescriptor(source, name)); | |
} | |
}); | |
} | |
/** | |
* Create a constructor function for a class implementing the given mixins. | |
* | |
* @param defaultOptions options that will be used if some options are missing at construction time | |
* @param mixins array of classes to be mixed together. The constructor of those classes will receive the options given | |
* to the constructor of the composed object | |
* @returns {{new(any): {}}} a constructor function | |
*/ | |
function compose(defaultOptions:any, mixins:any[]) { | |
// our constructor function that will be called every time a new composed object is created | |
var ctor = function(options:any) { | |
var o = {}; | |
// clone options given to the constructor | |
if(options) { | |
extend(o, options); | |
} | |
// complete with the defaultOptions | |
if(defaultOptions) { | |
extend(o, defaultOptions); | |
} | |
// call the constructor function of all the mixins | |
mixins.forEach(mixin => { | |
mixin.call(this, o); | |
}); | |
}; | |
// add all mixins properties and methods to the constructor prototype for all | |
// created objects to have them | |
mixins.forEach(mixin => { | |
extend(ctor.prototype, mixin.prototype); | |
}); | |
return ctor; | |
} | |
export = compose; |
import compose = require("./class-composer"); | |
import Swimmer = require("./swimmer"); | |
import Walker = require("./walker"); | |
import Talker = require("./talker"); | |
interface Duck extends Swimmer, Walker, Talker { | |
// constructor signature the typescript way | |
new(options?:DuckConstructorOptions):Duck; | |
(options?:DuckConstructorOptions):void; | |
} | |
interface DuckConstructorOptions { | |
sound:string | |
} | |
var Duck:Duck = compose({sound:"coin"}, [Swimmer, Walker, Talker]) as Duck; | |
export = Duck; |
import Duck = require("./duck"); | |
class MyApp { | |
duck:Duck; | |
constructor() { | |
this.duck = new Duck({sound:"puiock"}); | |
} | |
manageDuck() { | |
this.duck.talk(); | |
this.duck.swim(); | |
this.duck.walk(); | |
} | |
} | |
export = MyApp; |
class Swimmer { | |
public hasSwimmed:boolean; | |
constructor() { | |
// initialize value in constructor | |
// the constructor is transpiled in a constructor function | |
this.hasSwimmed = false; | |
} | |
swim() { | |
this.hasSwimmed = true; | |
} | |
} | |
export = Swimmer; |
class Talker { | |
public hasTalked:boolean = false; | |
// default value which will be overriden in constructor | |
private sound:string = ""; | |
// constructor can take a config object | |
// the same object will be given to all mixins | |
constructor(options:{sound:string}) { | |
this.sound = options.sound; | |
} | |
talk() { | |
this.hasTalked = true; | |
return this.sound; | |
} | |
} | |
export = Talker; |
class Walker { | |
//initialize value when declaring property | |
// when transpiled the assigment is made in the constructor function | |
public hasWalked:boolean = false; | |
walk() { | |
this.hasWalked = true; | |
} | |
} | |
export = Walker; |