aaronstacy aaronstacy - 1 month ago 22
TypeScript Question

Mixins in TypeScript

I'm playing around with TypeScript, and I've got a couple functional mixins,

Eventable
and
Settable
, that I'd like to mixin to a
Model
class (pretend it's something like a Backbone.js model):

function asSettable() {
this.get = function(key: string) {
return this[key];
};
this.set = function(key: string, value) {
this[key] = value;
return this;
};
}

function asEventable() {
this.on = function(name: string, callback) {
this._events = this._events || {};
this._events[name] = callback;
};
this.trigger = function(name: string) {
this._events[name].call(this);
}
}

class Model {
constructor (properties = {}) {
};
}

asSettable.call(Model.prototype);
asEventable.call(Model.prototype);


The code above works fine, but would not compile if I tried to use one of the mixed-in methods like
(new Model()).set('foo', 'bar')
.

I can work around this by


  1. adding
    interface
    declarations for the mixins

  2. declaring dummy
    get
    /
    set
    /
    on
    /
    trigger
    methods in the
    Model
    declaration



Is there a clean way around the dummy declarations?

Answer

Here's one way to approach mixins using interfaces and a static create() method. Interfaces support multiple inheritance so that prevents you from having to redefine the interfaces for your mixins and the static create() method takes care of giving you back an instance of Model() as an IModel (the <any> cast is needed to supress a compiler warning.) You'll need to duplicate all of your member definitions for Model on IModel which sucks but it seems like the cleanest way to achieve what you want in the current version of TypeScript.

edit: I've identified a slightly simpler approach to supporting mixins and have even created a helper class for defining them. Details can be found over here.

function asSettable() {
  this.get = function(key: string) {
    return this[key];
  };
  this.set = function(key: string, value) {
    this[key] = value;
    return this;
  };
}

function asEventable() {
  this.on = function(name: string, callback) {
    this._events = this._events || {};
    this._events[name] = callback;
  };
  this.trigger = function(name: string) {
    this._events[name].call(this);
  }
}

class Model {
  constructor (properties = {}) {
  };

  static create(): IModel {
      return <any>new Model();
  }
}

asSettable.call(Model.prototype);
asEventable.call(Model.prototype);

interface ISettable {
    get(key: string);
    set(key: string, value);
}

interface IEvents {
    on(name: string, callback);
    trigger(name: string);
}

interface IModel extends ISettable, IEvents {
}


var x = Model.create();
x.set('foo', 'bar');
Comments