How we moved from AngularJS to React

Migrating our internal Content Authoring Tool with zero downtime for our users

For the creation of Babbel’s learning content, we have built a customized Content Authoring Tool, which was originally built as an AngularJS(1.x) app with 80+ custom directives, 35 services and 1400 unit tests (see Simon’s blogpost back from 2017).

This tool is regularly used by approximately a third of all Babbel employees, so having it up and running smoothly is a number one priority for our team, which consequently was named the CAT team.

When the maintainers of AngularJS decided to rewrite their framework from scratch and recommended users to upgrade to Angular 2 (learn more), we knew we were up for a challenge.

At the same time, React’s ecosystem was growing rapidly (both in functionality and popularity) and Babbel’s frontend teams were standardizing around it, meaning we had access to in-house expertise.

As a code migration became inevitable, two things were clear to us: 

  1. We had to do an in-place migration, in order to allow continuous improvement of existing features and development of new ones.
  2. We wanted to align the tool to the company’s tech stack. 

Finally, the team made the decision in favor of React. We sat down, made a plan and finished ahead of schedule… well, that’s not what happened… but we did have a plan! Of course, we ran into some issues and nevertheless, finished the migration with no downtime for our users. 

Got your interest? Then read on below.

Setting up for a hybrid application

Top level features of our app were written mostly with a domain model bound through two-way data binding to a top level AngularJS directive with multiple view templates. For the equivalent React Components we wanted to preserve the domain model and update the views with a more unidirectional data flow. At the same time, we also wanted to add new features to the same app root, which was made possible by the hybrid capabilities of ui-router. It allowed us to use either an AngularJS directive or a React component as a root node of a view.

From directives to components

The idea was to migrate all components bottom-up, or create drop-in replacements for low level AngularJS components and keep the app structure at the same time.

For migrating an AngularJS directive we used react2angular, which is a library that allowed us to wrap React components into AngularJS directives and use those in the templates.

Create a React component

import React from 'react'

const MyComponent = (props) => {
  return (
    <div>
      <p>{props.title}</p>
<p>{props.description}</p>
    </div>
  );
}

Expose it to Angular

import { react2angular } from 'react2angular'

angular
  .module('my-component', [])
  .component('myComponent', react2angular(MyComponent, [title, 'description']))

Use it in your AngularJS code

<my-component
  title="'AngularJS 2 React'"
  description="'how to react 2 angular'"
></my-component>

Running into Issues

Domain Models

While wrapping React components and appending them back to the AngularJS tree came quite easily, data distribution was a bit more tricky when it was on multiple levels down the component tree.

For the state management, we investigated which library would be easy to share between parts of the application written in AngularJS and React. After looking at different options, we decided to go for Mobx. The idea was to wrap the domain models with Mobx observables and subscribe React components just to state updates they were interested in. Additionally, AngularJS’s two-way data bindings were functioning as before without any additional adjustments, since the models were treated as ordinary objects.

Bundle sizing

The bundle size of the application became quite big, since we were using React, AngularJS, Bootstrap wrapper for each of them and some other small framework specific libraries at the same time. In general this could become quite a problem in terms of loading times, user experience etc. Since this tool is used internally only on desktop devices, it didn’t cause any noticeable performance issues, so we didn’t need to think about another way of dealing with this.

Cleaning up the folder structure

Since AngularJS and React apps usually use different architecture patterns, it was challenging moving from an MVC folder structure to a more modular one. We actually moved all React components to a common folder and had to refactor them after into specific modules.

Testing

To make sure that everything would work as before for our users, we covered most of the features with integration tests written in Cypress.

The AngularJS codebase had good coverage of unit tests, written in Karma. Since this framework was only for AngularJS components, we needed to take some action here as well.

As part of the migration of a component, we also moved unit tests to Jest.

You made it so far, and so did we!

Would we do it again? Probably yes. Here’s why:

  • we migrated our application with zero downtime for our internal users
  • we were able to ship new features during the migration process
  • limited the numbers of newly created bugs 
  • reduced bundle size and tool frustration along the way 

Our way was obviously just one way of doing it (and it might not be the right way for everyone), but looking back (on our timeline) and taking all of the requirements into account, we are all very  happy with the result!

Header Photo by Avel Chuklanov on Unsplash

Want to join our Engineering team?
Apply today!
Share: