Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Typescript

How to Easily Bind VueJS to a Typescript or ES6 Class

5.00/5 (2 votes)
29 Mar 2017CPOL 12K  
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:

JavaScript
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:

Java
export class Timer {

	public counter: number = 0;

	public increment(): void {
		this.counter ++;
	}
}

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

Java
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 it 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:

JavaScript
// 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 = {};			

/*			
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 
			// (i.e., an inherited class has overridden 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!
							(this as any)[propertyName](...args);
			};
		}
	});

	// 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 (typeof (obj.constructor) !== "undefined" && 
	     typeof (obj.constructor.name) === "string" && 
	     obj.constructor.name !== "BaseController") {
							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	
});

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)