Skip to content

Instantly share code, notes, and snippets.

@nch3v
Last active November 4, 2016 13:59
Show Gist options
  • Save nch3v/4c8c0ac5e03f220462a9 to your computer and use it in GitHub Desktop.
Save nch3v/4c8c0ac5e03f220462a9 to your computer and use it in GitHub Desktop.
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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment