It won’t necessarily be an easy task to undertake, but in the world of software doing nothing and expecting things to continue to “just work” is worse.
Back in 2018, my software development team was faced with a tough decision. It was announced that the framework we’d been using for over 2 years to build our application of record, AngularJS, was entering long-term support (LTS). This meant that new features, security patches, and further support from Google teams regarding framework issues would essentially cease.
After evaluating our options including upgrading from AngularJS to Angular (the name for every version of Angular 2 and beyond) or migrating and rewriting our application in a completely new JavaScript framework: React. We ultimately chose to go with ReactJS.
In the following article, I’ll walk you through how to migrate an AngularJS application to React, as well as some of the considerations you’ll need to keep in mind planning your own migration strategy.
As I said, we evaluated a few different, popular JavaScript framework options but ultimately ended up settling on React, and here’s some of the main reasons we did.
Let me give you a code sample to illustrate my point.
Below are two snippets of code used on the main landing page of the app to display cards with links to various pages and functionality The first code snippet is written in AngularJS’s HTML syntax.
AngularJS code:
<md-toolbar class="transparent-toolbar">
<div class="md-toolbar-tools">
<div class="md-display-4" id="page-header">
Dashboard
</div>
<span flex></span>
</div>
</md-toolbar>
<div layout="row" layout-sm="column" class="button-row">
<div flex="33" layout-padding class="layout-padding flex-33">
<md-card class="clickableOptionCard" id="manageCard" layout-align="center center" ui-sref="manage">
<p class="fa fa-briefcase"></p>
<md-toolbar class="layout-align-center-center">Add/Drop</md-toolbar>
</md-card>
</div>
<div flex="33" layout-padding class="layout-padding flex-33">
<md-card class="clickableOptionCard" id="keyCard" layout-align="center center" ui-sref="keyManage">
<div ng-include="'dashboard/template/key.template.svg'" class="key-wrapper"></div>
<md-toolbar class="layout-align-center-center">Key Management</md-toolbar>
</md-card>
</div>
<div flex="33" layout-padding class="layout-padding flex-33">
<md-card class="clickableOptionCard" id="executeCard" layout-align="center center"
onClick="window.open('execution/AMT2','_blank')">
<p class="fa fa-cube"></p>
<md-toolbar class="layout-align-center-center">Execution</md-toolbar>
</md-card>
</div>
</div>
<div layout="row" layout-sm="column" class="button-row">
<div flex="33" layout-padding class="layout-padding flex-33">
<md-card class="clickableOptionCard" id="triangulationCard" layout-align="center center" ui-sref="triangulation">
<div ng-include="'dashboard/template/triangulation-icon.svg'" class="triangle-wrapper"></div>
<md-toolbar class="layout-align-center-center">Triangulation Report</md-toolbar>
</md-card>
</div>
</div>
The second code snippet is the same functionality written in React’s JSX syntax. Tell me in the comments which code looks cleaner and easier to read to you.
import React, { Component } from 'react';
import { NavLink } from 'react-router-dom';
import KeysImage from 'assets/images/key.svg';
import StatusImage from 'assets/images/pro-direct-circle-outline.svg';
import ReportsImage from 'assets/images/health-check.svg';
import AMT2Image from 'assets/images/truck.svg';
import Card from 'commonComponents/Card/Card';
import './Dashboard.scss';
class Dashboard extends Component {
render() {
return (
<div className='headerBorder'>
<hr />
<div className='dashboard'>
<Card>
<a href='/keys'>
<span className='image-wrapper'>
<img src={KeysImage} />
</span>
<h2>Key Management</h2>
</a>
</Card>
<Card>
<a href='/manage/'>
<span className='image-wrapper'>
<i className='icon_scan' />
</span>
<h2>Add/Drop</h2>
</a>
</Card>
<Card>
<NavLink to='/status/change' onClick={this.resetChangeStatusPageFlag}>
<span className='image-wrapper'>
<img src={StatusImage} />
</span>
<h2>Store Status</h2>
</NavLink>
</Card>
<Card>
<a href='/triangulation'>
<span className='image-wrapper'>
<img src={ReportsImage} />
</span>
<h2>Triangulation Report</h2>
</a>
</Card>
<Card>
<a href={this.state.AMT2Url} target='_blank'>
<span className='image-wrapper'>
<img src={AMT2Image} />
</span>
<h2>AMT 2.0</h2>
</a>
</Card>
</div>
</div>
);
}
}
};
}
export default Dashboard;
Ultimately the decision to migrate from AngularJS to React was agreed upon because it would benefit both the users and the development team. A win-win situation.
There are a number of options when it comes to application migrations based on how your existing application is built. Let’s go through some of the approaches out there now.
* While the last 2 options listed are approaches that some development teams chose, I do not recommend them. Relying on third-party community-built libraries or getting different JavaScript frameworks with very different approaches to how applications should be designed and architected to work together as one makes for unsustainable code: debugging applications will be more difficult and finding developers proficient in both frameworks will be more difficult as well. If at all possible, avoid these options for more clear-cut ones.
I covered some of the benefits gained by migrating from AngularJS to React earlier in this article, but there’s more you might not recognize right off the bat. Here are a few more to help convince you.
I have no doubt there are more benefits that come from migrating away from AngularJS and to React than I’ve listed here, but these are the ones that immediately came to my mind.
All of the above is not to say that migrating to React is pure sunshine and roses, there are challenges when taking this on. Here are some of the challenges you may run into along the way.
With all this talk of migration options, benefits, and challenges, you may think that’s all there is to this task, but there are a few more things I’d recommend you think about before committing.
I don’t mean to discourage anyone from doing a migration if it’s the best thing, but take the time to evaluate the options and tradeoffs - this will most likely be a longer term commitment for the team.
Ok, so it’s been agreed a migration from AngularJS to React is in the cards. Now, how to actually go about it?
Here’s a plan you can follow that my team used with success.
Migration of JavaScript frameworks is almost always not only up to the development team: product managers, designers, and upper management will also need to be on board with the plan. To assuage their fears, make lists of the pros and cons with a special focus on how it will benefit the users to show them it’s a beneficial and necessary step.
In addition to the pros and cons, take this chance to identify places where the application could be improved in its next iteration, as well as plan for potential pitfalls. This could be things like: removing features not used in the current app, redesigning experiences in collaboration with designers to follow best practices, and how to effectively remove the logic from the current app into its own service so the new app could display the correct data.
Depending on what style of architecture your application is following, diagram what the new application will look like so everyone’s clear.
For my team, it involved 3 new microservices to handle the logic and data transformation previously left up to the AngularJS application, 2 new databases, and 2 UIs that would work in tandem until the React app had all the functionality of the Angular app. Even if this architecture ends up evolving over the course of the migration, having a plan of how this could go from the beginning will be helpful as the migration progresses.
Build a very pared-down version of the application to show it can be done - there’s no better form of proof than a small, working example app. If the POC needs to be able to route back to the first, make sure this works, and if automated testing is part of your team’s development workflow, write a few tests with the new testing frameworks to serve as an example for the larger migration. This too should help give the dev team and business partners confidence this will work.
For our own POC, I built a very pared-down version of our app’s navbar and dashboard page, complete with links allowing it to successfully route back and forth between the React app and our existing AngularJS app.
Additionally, I wrote Jest and Enzyme unit tests to demonstrate how to go about unit testing the components, as test driven development (TDD) is another methodology my organization subscribes to. Jest is a unit testing framework that actually shipped with React if you use the Create React App CLI to make a new React project. And at the time, Enzyme was created by Airbnb and added additional functionality to Jest for even more robust testing. Before React Hooks (at the time we began the migration), this was the defacto unit testing combination. Later on, we used Jest and React Testing Library when Hooks were introduced to the project. Finally, I used Cypress.io for the end-to-end tests, as it was quick and easy to set up.
Unless the current app does everything in one single page, there are probably some pages or screens and functionality that will be easier to migrate to the new app than others. Think about pages that have more static components or where the display depends on data from backing services - these will be easier to tackle first. For parts of the AngularJS app that handle business logic, this logic will need to be extracted and rewritten into a separate microservice for the React app to consume and display. This will be one of the parts to migrate later in the process. Be sure to consult with the product managers as you make these decisions to take into account business needs and the product roadmap.
My own team, when we began our migration, determined our login page, dashboard, admin pages, and user profile pages would be the easiest to migrate, as they were the least reliant on business logic.
These pages would not only give our developers a chance to start getting familiar with how React worked, in less complex, data-driven scenarios, but could also be done in parallel with the developers building the new microservices to house the old UI’s logic.
The majority of the screens that were dependent on the business logic that had to be refactored out, were also identified, and the thinking was: the logic extraction would be done on the backend before those screens were greenlighted for migration to React.
Screens that were tied to logic included our checkout cart and all of the screens concerned with our users’ product assortments. For instance, in order to render the correct assortments, complex logic had to run in the Angular UI that took into account what was in our databases and combine it with what was in our users’ carts. In short, the UI was responsible for a lot of computationally intensive processes better left to backend services.
Most likely there’s not going to be much overlap between how the Angular components were built and how the same component will be rebuilt in React with JSX. When recreating the components in the new app, study the functionality of how they behave currently and work with the user experience designers to ensure the new components recreate the functionality but also adheres to any larger UX organization best practices. It’s always beneficial to build similar workflows in apps within an organization so the actions feel more familiar to users.
I’m sure at this point you’re wondering how my own team’s migration went. I’ll give you the highlights.
After going through all the steps outlined in the previous section to evaluate our options, assess the risks, prove out our theory with a small proof of concept, and come up with a strategy for the migration (including rewriting logic in the AngularJS app into new supporting Java applications), we set to work.
The full migration and eventual shutdown of our AngularJS app ended up taking 3 years. To accomplish this, we split the development team in two: half worked on migrating existing functionality to React, half worked on adding new features and functionality to React.
By the end, there were 3 new Java microservices, 2 new databases, 1 new React application, and 1 slimmed down AngularJS application (this Angular app was a temporary addition).
Progress was slower than expected. Extracting the business logic into standalone microservices took longer than anticipated, we went through 4 different databases before settling on a MariaDB, a subset of the development team was pulled away to assist setting up a new application to bring users who had been relying on Excel spreadsheets into the new millennium, and we also ended up building a thinned down AngularJS UI to test the new microservices before we’d recreated the functionality in React.
It was tedious maintaining existing functionality while simultaneously removing all the twisting, turning, logic once needed in the UI to handle the data. But it was also beneficial, as we were able to identify gaps in the data that the microservices missed early on in the process and fix it.
And in the end, it worked. The day the old AngularJS application was shutdown was a day when everyone breathed a sigh of relief.
Rewriting an application in a new framework is not an easy undertaking, but in many cases it’s the best way forward when an important business application just can’t pass muster. It may be that long term support for the app’s current framework is ending, the app is getting slow for users and harder to maintain for developers, or functionality needed by the users just isn’t possible with the current technology.
Whatever the reason, although it seems daunting, it is very possible: my own development team did it when we migrated from AngularJS to React. We mapped out a plan of attack beforehand, took a full assessment of our options, and built a proof of concept before we dove in. In the end, the migration was a success. Not everything went strictly to plan, but we were able to persevere and adapt as the migration progressed.
This is certainly not the only way to approach such a migration, but I hope it gives you some points to keep in mind if you are faced with a similar situation.
Good luck!