How to easily bind VueJS to a Typescript or ES6 class

How to easily bind VueJS to a Typescript or ES6 class

Like many, I have been very impressed with the work done on VueJS and TypeScript and naturally I want to leverage the benefits of each in my projects.  Unfortunately the Vue configuration model does not facilitate binding to a class as such.  Instead Vue asks for a bag of methods & properties via its constructor:

var app = new Vue({
    el: document.getElementById('app'),
    data: {counter: 1},
    methods: {
        increment: function(){
            this.counter ++;
        }
    }
});

But this is not the way modern Javascript applications are written:

  • Javascript components are encapsulated in classes – now supported natively in ES6 and of course in Typescript
  • Regardless of how you build your components, they should be agnostic towards any UI binding (if any) being employed against them

So, for example, the above component would be encapsulated in the following Typescript module:

export class Timer {
    public counter: number = 0;
    public increment(): void {
        this.counter ++;
    }
}

It would be nice if Vue let you do something like:

new Vue({el: xyz, model: new Timer()})

…but of course it doesn’t, and hence why you are reading this post.  Well, we had this problem but with a little determination we wrote this method which maps any class into a Vue instance.  We’ve been using for a few months now and haven’t noticed any departures from normal Vue behaviours.  I’ve self-documented inline below.  Please grab and use/modify as you wish:

private BindDataToTemplate(): void{
  // Vue requires an array of functions/methods to pass to its 'methods' property, so we create an empty list here and populate below using reflection

  let functions: any = {};
  let hasBoundBaseController: boolean = false;

  /*
  fn_bindFunctions
  Helper routine to iterate up the inheritance model, binding properties/functions as required. 
  For example, if you are binding Car, which inherits from Vehicle, this will
  make sure that Vue has access to the properties in Vehicle
  */
  let fn_bindFunctions = (obj: any) => {
      // Get all the properties and functions etc of this object
      let fnList = Object.getOwnPropertyNames(obj);

      // Iterate through each property/function we have found
      fnList.map((propertyName: string) => {
          if (typeof (propertyName) !== "string") return;
          if (propertyName === "constructor") return;
          // Map the function
          if ((this as any)[propertyName] instanceof Function) {
          // Break if we've already got this function name (ie. an inherited class has overriden it)
          if (typeof (functions[propertyName]) !== "undefined") return;
          // Map to our VUE object
          functions[propertyName] = (...args: any[]) => {
                  // By using a closure and referring to 'this' we change the javascript context of the event handler from 'vue', back to this actual controller!
                  return (this as any)[propertyName](...args);
              };
            if (propertyName === "BindDataToTemplate") hasBoundBaseController = true;
          }
      });

      // Bind parent object (ie. the class that this object inherits from) - note that BaseController is the name of our base class, from which all objects inherit
      if (!hasBoundBaseController) fn_bindFunctions(Object.getPrototypeOf(obj));

  };
  // Kick off function binding with the current instance
  fn_bindFunctions(Object.getPrototypeOf(this));

  // Create our vue bindings
  var app = new Vue({
      el: document.getElementById('app'),
      data: this, // Binding the data to 'this' works well
      methods: functions // Bind the methods to the array of functions we created above 
  });
}
The development process

The development process

Using the Microsoft Dynamics CRM Online Web API from your ASP.Net MVC website

Using the Microsoft Dynamics CRM Online Web API from your ASP.Net MVC website