/ Engineering

Why and How We Migrated from AngularJS to VueJS

As I'm writing this, we've just removed the last line of AngularJS code from our application codebase, ending a 4-month non-intrusive effort to migrate our application from AngularJS to VueJS. In this post, I'm going to share our experience going through the process.

Some Background

Our application (Holistics.io) is a SQL-based Business Intelligence (BI) Platform written with Rails, Sidekiq, PostgreSQL and AngularJS. Our Rails app started in late 2013 as a simple app with jQuery and AngularJS. We used AngularJS mainly for the following features/functionalities:

  • View model binding (controller, view + template engine)
  • Dependency Injection (services, factories, directives)
  • Angular 3rd party components (uib-modal, ui-select, ...)

The rest are mostly in-house, custom JavaScript.

Our Problems with Angular

As our application grew, these are the problems we're facing with AngularJS:

  • Rendering Performance: As a data tool, a lot of the time we have to render a large table of data, and rendering them with AngularJS yield pretty bad performance.

  • Angular documentation is bad: This isn't a big deal until it is. The more we work with AngularJS, the more we realized that their documentations are really difficult to comprehend.

  • Two-way data flow makes handling logic very difficult, both in term of writing components, as well as writing view controllers. This is perhaps the most important reason of all.

Considering Different Frameworks

Before making a decision, we took a hard look at our different options:

Angular 2

We did spend some time looking into Angular 2. And to us, Angular 2 is somehow even more confusing than Angular 1. There are too many new changes (TypeScript), new template syntaxes, etc that we felt didn't really address our core problems. Furthermore, the migration path from v1 to v2 felt a bit fuzzy to us at the time.

ReactJS

We took a hard look into ReactJS. While we really liked the philosophy and the ecosystem, the biggest thing that struck us is we couldn't figure out a clear, clean, gradual migration path that doesn't stop us from supporting new features for 3-4 months.

AngularJS follows html-based templating system, while ReactJS is JSX, and we couldn't figure out a way to let both of them live nicely together during the migration.

Another minor, subjective reason is, I found JSX rather lengthy compared to HTML-based templating.

EmberJS

EmberJS is the framework for web applications, not a JS library, we would have to rewrite the entire thing in EmberJS.

Why We Went With VueJS: Incremental Migration

There are a combination of reasons that we chose VueJS in the end, but the biggest and most defining factor for us is: we saw a clear, incremental migration path to VueJS without disrupting our development roadmap. In fact, I bet none of our customers ever notice any visible changes at all during the whole period, they wouldn't know which page they visit is Angular-based, which page is VueJS-based.

Vue took the similar component-based, props-down-events-up approach of ReactJS, yet surprisingly similar to AngularJS in term of templating engine. It feels like the right mix of AngularJS and ReactJS combined. This works perfect for us, since we have a lot of AngularJS templates, and our main problem is the logic complexity introduced while using Angular components.

In fact most of the time what we needed to do was to change our code from ng- to v-. This is brilliant!

The more we dug into it, the more we realized we're making the right choice that solved all our earlier problems: great performance, single file component, very clean code structure, slots, etc.

Also, during the migration process, because of the way Vue structures (one way data flow, component-based), it forced us to rethink and refactor rather poorly written code, thus greatly simplify our code logic.

One last point that I'd like to mention, is I find VueJS documentations extremely well-written and well-structured. This is another major feature why we chose VueJS. When I was first introduced about Vue, I spent 30 minutes reading its documentation, and felt immediately compelled to try out.

How We Incrementally Migrated:

Below are simple steps we took for the migration (note that this is related to our context of running a Rails app, if you're not using Rails, some of these might be different):

1. Convert AngularJS controllers' logic to VueJS

For an incremental migration strategy to work, we do the minimum amount of work needed to introduce VueJS to our code. Thus we start by changing our standard AngularJS controller + template files and introduce Vue to it:

// user_edit_controller.js.es6`
import Vue from 'vue'

app.controller('UserEditCtrl', ['$scope', '$http', 'Ajax', 'Util', 'Modals',
  function ($scope, $http, Ajax, Util, Modals) {
    let vapp = new Vue({
      el: '#v-wrapper',
      components: {
      	...
      },
      data: {
      }
    });
  }
]);
<!-- `users/edit.html.erb` -->

<div ng-controller="UserEditCtrl">
  <div id="v-wrapper">
    <!-- vuejs logic goes here... -->
    <input v-model="username" placeholder="Username" />
    ...
  </div>
</div>

This allows us to introduce Vue logic into our app, without changing any of the underlying app's frontend structure. It greatly helps us mitigate risks of making mistakes, as we can just quickly spend 1-2 hours converting a small portion of AngularJS to VueJS, test and deploy, without having to worry of causing regression bugs.

2. Convert AngularJS services to ES6 modules

First, we need to find a replacement for $http service, which we use the most among the AngularJS. For this, we simply change to using axios. We don't use $http$ directly but build a wrapper abstraction around it, so changing it is pretty simple.

Then, we have a lot of AngularJS services defined as:

// users.js
app.service('Users', ['$http', 'Ajax',
  function ($http, Ajax) {
    this.create = function(user) {
      // ...
    }
  }
]);

We simply change them to using ES6 class:

// users.js.es6
export default class Users {
  static create(user) {
    // ...
  }
}

If there's still Angular code in other controllers using these services, we clone the services to using ES6 Class, and keep the old Angular copy until no other Angular code uses this anymore. This duplicates our code for a while, but we just need to be careful about this, and make extensive comments in the code to link between the 2 copies.

3. Replace AngularJS controllers with VueJS components

After doing the above 2, some AngularJS controllers can be completely moved over to using VueJS, so similar to step 2 above, we swap out the app.controller() definition with using ES6. But this time, we then use Vue's Single File Component:

File: user_edit.vue

<template>
  <div>
    <!-- vuejs logic goes here... -->
    <input v-model="username" placeholder="Username" />
    ...
  </div>
</template>

<script>
import Users from 'users.js.es6'
export default {
  data: {    
  },
  methods: {   
  },
  mounted() {
    // initializing
  }
 
};
</script>

4. Add mounting entry point for Rails controller/view

By default, on every rails page load it will render the view. With the above Vue's Single File Component, we then change our Rails view users/edit.html.erb file to:

<div class="v-user-edit">
  <user-edit></user-edit>
</div>

And then put a code to mount it to the correct Vue component when the page loads:

import UserEdit from 'user_edit.vue' 
let vueConfig = {
  el: '.v-user-edit',
  components: {
    UserEdit
  }
};
new Vue(vueConfig);

In practice, the above JS code is abstracted into reusable function to be called in different pages.

Conclusion

We finished migrating our framework late September 2017, about 4 months of non-intrusive work (we continue adding other features while working on the migration). In fact we didn't really put the migration at our top priority, whenever we need to make a change that touch old Angular code, we first converted it to Vue, and then make our change.

After the migration, now we have achieved:

  • Very clean code and modular (component-based), VueX and Vue Store; these greatly increased programming productivity
  • No more complex logic
  • Enhance UI performance

This is not to say Vue is the best, it simply worked best for our specific case: an Angular-heavy application that hits some limitation due to inherent design nature of Angular, and Vue filled that gap perfectly for us, with a very natural, incremental path of migration.

What are your thoughts? Do share with us in the comments below. Would love to learn more about your case!

— — — — — — — — — — — —

Having problems finding a simple and affordable data reporting system for your startups? Check us out at holistics.io.

Huy Nguyen

Huy Nguyen

Original creator and cofounder of Holistics, a data platform for tech companies. Holistics’ customers are tech startups like Grab, Traveloka, ShopBack, 99co, Tech In Asia and alike.

Read More