DEV Community

Cover image for Migrating from KnockoutJS to VueJS
Jesal Gadhia
Jesal Gadhia

Posted on • Originally published at jes.al

Migrating from KnockoutJS to VueJS

Recently I've been shopping around for a framework to replace KnockoutJS in an existing application. While KO has served it's purpose well, over the years it hasn't been maintained very actively and has largely failed to keep up with the newer JS frameworks in terms of features and community adoption.

After doing some research to find it's replacement, I settled on VueJS. It seemed to be most aligned with Knockout's MVVM pattern while at the same time being modular and extensible enough to serve as a complete MVC framework if needed using its official state management & routing libs. Above all, it seems to have a flourishing community which is important when it comes to considering a framework.

So as a KnockoutJS developer, let's go through some of the most familiar aspects of the framework and see how it translates to VueJS.

Viewmodel

In KO, the VM is can be as simple as a object literal or or a function. Here's a simple example:

var yourViewModel = function(args) {
  this.someObv = ko.observable();
  this.someObv.subscribe(function(newValue) {
    //...
  });
  this.computedSomeObv = ko.computed(function() {
    //...
  });
  this.someMethod = function(item, event) {
    //...
  }
};
Enter fullscreen mode Exit fullscreen mode

Usage:

ko.applyBindings(new yourViewModel(someArgs), document.getElementById("element_id"));
Enter fullscreen mode Exit fullscreen mode

VueJS has a very similar concept although the VM is always an object literal passed into a Vue instance. It also provides much more structure and richer event model. Here's a VM stub in VueJS:

var yourViewModel = new Vue({
  data: {
    someKey: someValue
  },
  watch: {
    someKey: function(val) {
      // Val has changed, do something, equivalent to ko's subscribe
    }
  },
  computed: {
    computedKey: function() {
      // Return computed value, equivalent to ko's computed observables
    }
  },
  methods: {
    someMethod: function() { ... }
  },
  created: function () {
    // Called synchronously after the instance is created.
  },
  mounted: function () {
    // Called after the instance has just been mounted where el is replaced by the newly created vm.$el
  },
  updated: function () {
    // Called after a data change causes the virtual DOM to be re-rendered and patched.
  },
  destroyed: function () {
    // Called after a Vue instance has been destroyed
  },
});
Enter fullscreen mode Exit fullscreen mode

I didn't list all the event hooks in that example for brevity. I recommend checking out the this lifecycle diagram to get the full picture.

VueJS also offers an interesting way to organize and share common code across VMs using something called Mixins. There are certain pros and cons of using a Mixin vs just a plan old JS library but it's worth looking into.

Usage:

yourViewModel.$mount(document.getElementById("element_id"));
Enter fullscreen mode Exit fullscreen mode

Something to note about the syntax above, it is entirely optional. You can also set the value of the el attribute in your VM to #element_id and skip explicitly calling the mount function.

Bindings

The concept of bindings is something KO developers are very familiar with. I'm sure throughout the course of working with KO, we've all created or used a lot of custom bindings. Here's what the custom binding stub looks like in KO:

ko.bindingHandlers.yourBindingName = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // This will be called when the binding is first applied to an element
    // Set up any initial state, event handlers, etc. here
  },
  update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // This will be called once when the binding is first applied to an element,
    // and again whenever any observables/computeds that are accessed change
    // Update the DOM element based on the supplied values here.
  }
};
Enter fullscreen mode Exit fullscreen mode

Usage:

<span data-bind="yourBindingName: { some: args }" />
Enter fullscreen mode Exit fullscreen mode

VueJS has something similar but it's called a "directive". Here's the VueJS directive stub:

Vue.directive('yourDirectiveName', {
  bind: function(element, binding, vnode) {
   // called only once, when the directive is first bound to the element. This is where you can do one-time setup work.
  },
  inserted: function (element, binding, vnode) {
    // called when the bound element has been inserted into its parent node (this only guarantees parent node presence, not           // necessarily in-document).
  },
  update: function(element, binding, vnode, oldVnode) {
    // called after the containing component has updated, but possibly before its children have updated. The directive’s value may     // or may not have changed, but you can skip unnecessary updates by comparing the binding’s current and old values
  },
  componentUpdated: function(element, binding, vnode, oldVnode) {
    // called after the containing component and its children have updated.
  },
  unbind: function(element, binding, vnode) {
    // called only once, when the directive is unbound from the element.
  },
})
Enter fullscreen mode Exit fullscreen mode

Usage:

<span v-bind="{yourDirectiveName: '{ some: args }' }" />
Enter fullscreen mode Exit fullscreen mode

As you can see VueJS offers a couple of additional life-cycle hooks, but on the most part, its very similar to KnockoutJS. So transferring old bindings into new directives isn't too difficult.

In most cases you should be able to move everything in your init function into the inserted function. As far as the update function goes, it will largely remain the same but you can now compare the vnode and oldVnode to avoid necessary updates. And lastly, if your custom binding used the KO's disposal callback i.e ko.utils.domNodeDisposal.addDisposeCallback you can move that logic into the unbind function.

Another thing you'll notice is that the usage syntax is a bit different, instead of using the data-bind attribute everywhere, VueJS uses different attributes prefixed with v- for various things such as v-bind for binding attributes, v-on for binding events, v-if/for for conditionals/loops, etc.

To add to that, there is also a shorthand syntax for those which might make things confusing initially and it's probably the biggest gotchas for devs transitioning from Knockout to Vue. So I recommend taking some time to go through the template syntax documentation.

Extenders

Another tool in KO that we are very familiar with is the concept of extender which are useful for augmenting observables. Here's a simple stub for an extender:

ko.extenders.yourExtender = function (target, args) {
  // Observe / manipulate the target based on args and returns the value
};
Enter fullscreen mode Exit fullscreen mode

Usage:

<span data-bind="text: yourObv.extend({ yourExtender: args })" />
Enter fullscreen mode Exit fullscreen mode

Closest thing to extenders in VueJS is the concept of "filters", which can be used to achieve a similar objective. Here's what a filter stub would look like:

Vue.filter('yourFilter', function (value, args) {
  // Manipulate the value based on the args and return the result
});
Enter fullscreen mode Exit fullscreen mode

Usage:

<span>{{ "{{" }} yourVar | yourFilter(args) }}</span>
Enter fullscreen mode Exit fullscreen mode

Alternatively you can also call a filter function inside the v-bind attribute

<span v-bind='{style: {width: $options.filters.yourFilter(yourVar, args)}}'/>
Enter fullscreen mode Exit fullscreen mode

Components

KO offers the ability to create components to help organizing the UI code into self-contained, reusable chunks. Here's a simple component stub:

ko.components.register('your-component', {
  viewModel: function(params) {
    this.someObv = ko.observable(params.someValue);
  },
  template: { element: 'your-component-template' },
});
Enter fullscreen mode Exit fullscreen mode

Usage:

<your-component params='someValue: "Hello, world!"'></your-component>
Enter fullscreen mode Exit fullscreen mode

VueJS also has the ability to create components. They are a much more feature rich and have better lifecycle hooks compared to KO. They also feel more much "native" to the framework. Here's a simple component stub in Vue:

Vue.component('your-component', {
  props: ['someValue']
  data: function () {
     return {
       someKey: this.someValue
     }
  },
  template: '#your-component-template'
})
Enter fullscreen mode Exit fullscreen mode

Usage:

<your-component someValue="Hello, world!"></your-component>
Enter fullscreen mode Exit fullscreen mode

This just scratches the surface of what's possible with components in Vue. They are definitely worth diving more into. Maybe I'll cover them more in another post.

3rd Party Plugins/Libs/Tools

Mapping - One of the commonly used plugin the KnockoutJS ecosystem has been ko.mapping plugin which helps transforming a JavaScript object into appropriate observables. With VueJS, that is not needed since Vue takes care of that under the hood by walking through all the properties of a VM and converting them to getter/setters using Object.defineProperty. This lets Vue perform dependency-tracking and change-notification when properties are accessed or modified while keeping that invisible to the user.

Validation - In addition to mapping, Knockout-Validation library is another mainstay of the ecosystem. With VueJS, vee-validate is it's popular counterpart and provides similar features out of the box.

Debugging - It's important to have a good debugging tool for development. KnockoutJS has Knockoutjs context debugger, while VueJS offers something similar with Vue.js devtools

Lastly...

VueJS is an incredibly feature rich framework with various options for customization and code organization. It is one of the fastest growing frameworks with adoption from some big names projects such as Laravel, GitLab, and PageKit to name a few. Hopefully that will make it a good bet for the future!

I'll leave you with this chart which pretty much sums up the story of these two frameworks:

Screen Shot 2017-05-29 at 10.36.00 PM.png

This post was originally published on my blog. If you liked this post, please share it on social media and follow me on Twitter!

Top comments (0)