Integrating React Into A Legacy AngularJS App

Geoff Chan

Co-founder

4 min

Article Banner

Overview

Our partners often come to us with unique technical challenges. Embedding a React app into an AngularJS app may seem like a strange ask. In this case study, I will highlight the business need that created a case for us to do this, how we approached the situation, and the results of the final solution.

The Challenge

Our partner, adidas, first approached us about the need to integrate two existing systems (I can't get into details about the systems so I will stay high level). One team had a legacy AngularJS app with a lot of front-end business logic, which they were running for their B2B platform. Think internal e-commerce website. Another internal dev team (different country's business unit working on a similar platform) had created a React application that allowed for a custom add-on to products. Think personalized clothing for sports teams. After some discussion about approaches we came to a few conclusions:

  1. There was not enough time and resources to build the functionality into the legacy AngularJS app
  2. We had to keep the user in the AngularJS app because of all of the business logic
  3. We had not done anything like this before
We set the right expectations from the beginning and didn't promise something that we couldn't deliver.

I just wanted to make clear that we were completely transparent and upfront about never doing something like this before. I think that this is a big reason why the project was ultimately a success for all parties. We set the right expectations from the beginning and didn't promise something that we couldn't deliver.

Weighing the Options with a Proof of Concept

We decided that the best way forward would be to build a proof of concept. This would allow us to do a few things:

  1. Determine if it is feasible
  2. Get a more realistic idea of the effort involved
  3. Get more info to make a better decision with relatively low time investment

Using ngReact

After doing some research (a.k.a Googling 'integrating react into angularjs') and consulting some colleagues that had done similar integrations, we decided to try using ngReact. ngReact is an Angular module that allows React components to be used in AngularJS applications. This seemed like a good place to start because it had 2.6k stars on Github and (at the time) was actively maintained. It also seemingly did what we needed based on the documentation. Our code looked something like this (obviously a contrived version just to illustrate the idea):

1app.directive('reactApp', function(reactDirective) {
2  return reactDirective('ReactAppEntryComponent');
3});
1angular
2  .module('app', ['react'])
3  .controller('productController', function($scope) {
4    $scope.options = {
5      // various options to initiate the React app with
6    }
7  });
1<div ng-controller="productController">
2  <react-app options="options"></react-app>
3</div>
We decided to take a step back and try a different approach.

This did not work. We had missed one key thing in our technical exploration. The React app required a Redux store. For this we used ngRedux. But as we continued to dig deeper we encountered more challenges. We found out that Redux Thunk middleware was also a dependency of the React app. We found that ngReact did not allow for us to pass props down to nested children. We tried to import the libraries directly, but there were many issues trying to import modern JavaScript dependencies into a legacy app that still used Grunt to build/compile. We decided to take a step back and try a different approach.

Going back to vanilla JS

Instead of trying to make this work the "Angular" way, we decided that we would just try to make this work. We included the UMD versions of the dependencies we needed manually in the index.html file.

1  <script src="https://unpkg.com/react@16.7.0/umd/react.production.min.js"></script>
2  <script src="https://unpkg.com/react-dom@16.8.4/umd/react-dom.production.min.js"></script>
3  <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.1/redux.min.js"></script>
4  <script src="https://unpkg.com/react-redux@6.0.1/dist/react-redux.min.js"></script>
5  <script src="https://unpkg.com/redux-thunk@2.3.0/dist/redux-thunk.min.js"></script>

Then we created a Redux store, applied the Thunk middleware, and initiated the React app.

1angular
2  .module('app', ['react'])
3  .controller('productController', function($scope) {
4    $scope.options = {
5      // various options to initiate the React app with
6    }
7
8    var store = Redux.createStore(
9      Redux.combineReducers({ reducer: ReactApp.reducer }),
10      Redux.compose(Redux.applyMiddleware(window.ReduxThunk.default)) // Uses ReduxThunk.default because of the UMD build
11    );
12
13    ReactDOM.render(
14      React.createElement(ReactRedux.Provider, {store: store}, [
15        React.createElement(ReactAppEntryComponent, ReactAppProps, null)
16      ]),
17      document.getElementById('reactApp')
18    );  
19  });
1<div ng-controller="productController">
2  <div id="reactApp"></div>
3</div>

We were able to access all of the libraries globally (avoiding any compiling issues) since we included them with script tags. We created a simple <div> to bootstrap the React app. Other than a few styling issues, this actually worked, so we brought this back to the teams at adidas.

The Result

As a team, we decided to pursue this method and build out a more robust implementation. It was by no means a perfect (or pretty) solution, but it was a practical and viable solution that solved the problem. Some further challenges and drawbacks that we encountered were:

  1. Figuring out a way to implement CI/CD for the React app
  2. Implementing an imperative approach in a largely declarative codebase
  3. Sharing and managing state
It was by no means a perfect (or pretty) solution, but it was a practical and viable solution that solved the problem.

But all in all, this was a win. We were able to successfully integrate two complex systems. We made sure to do knowledge transfers so that there was an internal understanding of the implementation. And ultimately our partner was happy, so we were happy.

Profile Pic

Roman ChocinaDirector IT

adidas

Throughout our partnership, Stack Five has provided us with amazing expertise in helping us to build new products and upgrading legacy B2B applications