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 }); }