Preparing a Large Scale Application for Angular Migration

A checklist approach to reduce the complexity and risk

Large Friendly Letters

Migrating from AngularJS to Angular is hard, really hard, even with the various migration utilities provided by Angular. The complexity of a migration only grows the larger the project is. However, there is hope! By taking a few steps before starting the migration it is possible to reduce its complexity and the risk of running into problems.


This is part 2 of our series covering the common pitfalls faces in the Angular Migration Process. You can read the first part here.

My name is Florian and I work on the frontend team here at Panaseer where we a currently half way through our migration from AngularJS to Angular 5. The goal of this post is to share with you the actions that came out of our pre-migration research, explain how they have helped us and provide a simple checklist you can use to prepare for your migration.

Note: I refer to AngularJS 1.6 and below as AngularJS and Angular 2 and above as Angular.

Best Practises

AngularJS has changed a lot since its first introduction and with that so have its best practices. Updating to a new version is easy (usually) however updating your practices is much harder. This is especially true when it means going back and refactoring previously written code, which you might not have touched for months or years. Therefore it becomes very easy to stick with outdated practices.

However, this comes back to bite you when you attempt to migrate to Angular using upgrade utilities designed to migrate code based on the latest best practices. Now, I wouldn’t suggest doing an upfront rewrite of all the legacy code to follow the ideal practices but ensuring you have an understanding of the quality of the different sections of your application and perhaps updating the more critical parts.

The Angular community has developed a few key best practices over last couple of years, which if not followed will make the migration process a lot more complex.

The most obvious would be to use a modern Javascript flavour, whether this is ES6 or Typescript. Both provide essential functionality that the rest of the actions will rely upon such as modules and classes. Using Typescript has become the recommended approach by the Angular team although it’s still very much possible to use Angular with ES6 and Babel. We had previously already made the decision to introduce Typescript into our project to enable type safety and object-oriented patterns (best decision ever!). To prepare for the Angular migration we just had to convert our remaining Javascript files to Typescript.

Before modules, the suggested way of doing things was registering each of the components/directives to their modules in the same file as they are defined and using Immediately Invoked Function Expression (IIFE). The problem with this is it gave no clear picture of the dependency tree and required massive lists of scripts in the index html file. Using modules can provide this clarity. See the before and after comparison below to see how we took advantage of them:

Pre ES6 modules
Post ES6 modules

Components were introduced in AngularJS 1.5 in response to the trend towards component-based web development. Components provide clearer grouping of templates/logic, stricter scope isolation and clearer input/output communication. Angular has embraced this component approach which means migration becomes much easier if you already use components.

Reduce Dependency on Angular

Angular is great and all, however the web is constantly changing and frameworks come and go. So to build an application, that lasts the test of time, it is best minimise the reliance on the frameworks.

Services in Angular/JS provide a great way to store and communicate state for a part of your application as they are a singletons available anywhere in the application. However we quickly found ourselves using services as a place to hold non stateful (side effect free) functions that we wanted to reuse across the application. By breaking these function out as exportable members of stand alone ES6 utility modules we now have clearer dependency tree, less dependency injection overhead, tree shaking and finally no need to migrate.

Example of a couple math utility functions

Even in some cases where state is required, we still have been able to replace factories and some services with classes which keep state.

However this becomes complex when these functions depend on a function from another services. To avoid going down the refactoring rabbit hole its best to start with the lowest level utility functions first.

Another big advantage of this decoupling is that in future these utilities could be split into their own project and re-used across multiple applications.

Bridge the Gap

To reduce the boilerplate code and make its purpose clearer Angular taken advantages of decorators. Using Typescript or Babel you are able to use similar decorators with AngularJS, which makes the AngularJS code cleaner and eases the migration to Angular.

The options are to use an existing Angular like AngularJS decorator library such as ng-metadata or produce your own. We produced our own lightweight set of decorators to match some of the Angular functionality. Since then, ng-metadata has come a long way and likely represents the best choice.

For performance or clarity reasons numerous commonly used services/functions/directives/filters from AngularJS will no longer be in Angular. Examples of functionality that have no clear replacement include: $scope/$rootScope, $broadcast, $emit, $compile, ng-include, $parse, $q. For all of these we identified where we used them and produced a plan to refactor and remove them.

Another area which can cause problems when migrating, is the use of third party libraries and components. For the most part libraries without direct ties to Angular should work fine after the migration, however we found ourselves using numerous libraries that did rely on AngularJS. For each of these we decided on an alternative that was compatible and either implemented straight away or as part of the migration. Examples of these include UI-Router(for which we will soon release a blog post on how we migrated it) and specific AngularJS visual components.

Develop your Strategy

Before beginning the migration of a large application you should develop a thorough strategy. This comes down to three main points:

  1. Time frame
  2. Order of approach
  3. Migrate or rewrite

In regards to time frame we decided on a gradual migration as we are a small team and could not halt delivering value to our customers for the months it would have taken us to migrate our entire code base. So this means all new features will be written in the latest Angular and we will migrate the existing components/services as we need them or have time. We started by migrating our user management sections as it was the newest and most isolated high level module.

Another key aspect of our migration strategy, was the decision to prefer downgrading Angular components as opposed to upgrading AngularJS components. We went down that route for two main reasons:

  • To encourage the migration of components rather than just upgrading them
  • Downgrading uses only the downgrade function whereas upgrading requires the creation of a whole wrapper component.

This led to a bottom up approach to migrating which we are doing for each high level application module.

However not all code should be migrated! All projects will eventually build up legacy code that really could do with a refactor. In many of these cases the effort to refactor and migrate would be similar to that of doing a total rewrite. We found that for large sections of existing code, we were able to combine the refactor and migration by rewriting them in the latest Angular as part of ongoing work. For example, as part of a recent redesign of our dashboards, we did a total rewrite in Angular. This was instead of performing the complex refactor of the dashboards and migrating them (in either order). This was no doubt faster than the combined time of a refactor and migration.

Checklist

It’s dangerous to go alone! Take this. ⚔️ — Old Man

The above may sound like a lot of work to do before even starting the migration… however without taking these steps our migration would have been far more time consuming.

We found splitting it up into more approachable chunks helped us understand how we where doing and whether we were ready to move to the next phase. Hence we have created this checklist!

  • Use a modern Javascript flavour(Typescript or ES6)
  • Replace IIFE with import/export modules
  • Use components instead of directives or controllers
  • Move side effect free functions from services to utility files and import where needed
  • Replace factories and in some cases services with classes
  • Use decorators to reduce syntax differences between the frameworks
  • Evaluate and remove use of functionality no longer available in Angular
  • Evaluate third party libraries compatibility with Angular
  • Decide the migration time frame
  • Decide the order to approach migration
  • Decide beforehand whether to migrate or rewrite

All the best! 🚀

We hope this can save you some time and frustration. Let us know in the comments or by tweeting us @panaseer_team how your migration process is going and how your are approaching it!

Also watch this space for our next post covering how to configure routing within a hybrid application.