Migrating AngularJS to React and Keeping it Sane
Back in the days of the wild wild web (www) and post JQuery era, one web framework stood above all others: AngularJS. A “ring to rule them all”, AngularJS consolidated quite a few micro-frameworks and provided many extensibility points of expansion if needed. Over time though, many performance and architectural questions began to arise, to the point of no return – when the guys @Google decided to migrate from AngularJS to Angular (a poor naming decision). The time came when we were forced to decide whether to migrate to Angular or to a different FW.
Embedding React Component Inside AngularJS Directive
As this post’s title suggests, we decided to migrate to React (for many reasons, which could take a whole other post to explain). However, doing so required us to support a variety of flavors in our app where React components would reside inside AngularJS directive and vice versa. The path toward achieving it was not straight forward and required that we think creatively.
We also knew that we wanted our React app to work in a new environment using WebPack and other “new goodies” while keeping the Angular side operating with previous tooling such as Gulp. That being said, we created a new repository for React which would later be consumed by Angular as a Bower/NPM package, then registered our main React components globally, enabling the consumption of such components using ng-react directive later on. This was easily achieved with a system link between the two projects.
The main point of interest was the router. We wanted React to control the router for new pages in our app and to keep Angular in control of existing pages. After a lot of trial and error, we found the best solution was to keep all React pages under a distinctive root – let’s call it “react” which made our URL look like this:
Using angular-ui-router, we simply set React components under a “react” URL root, like so:
Our reactCtrl used ng-react to set the React root:
Then we set the Angular router to render an Angular controller, which later would render a React component using ng-react. The React router could take control of everything that comes after a “react” URL root.
One more thing – and yes, we realize this has already been complicated enough – in our case, AngularJS was in control of our application menu, but in many cases you’ll want to navigate from the application menu deeper into React pages (not just the first level). For that, we ended up doing the following:
Doing so allowed the Angular app menu to have deep navigation links into React pages.
Embedding AngularJS Directive Inside a React Component
While we ultimately wanted to switch from AngularJS to React, we still had many complex components written in AngularJS and it would take some time to perform a full migration. If we wanted to keep React as our main dev platform, we needed a way to embed AngularJS directives inside React components.
Once you have AngularJS set up on the page, you can compile any AngularJS directive regardless of its location in the DOM. That made it possible to have an AngularJS directive under a React component. We ended up with something like this:
When the component did mount, we got the DOM reference, which is stored under “this.container”. For that element, we created an inner HTML with an AngularJS directive. In order to compile the directive, we needed to pass a scope and DOM element. Once we did, we were able to set any object on the scope and allow the data-binding magic to happen.
So how do we get a new isolated scope?
Since we had the “luxury” of tightly coupled frameworks, we knew AngularJS would always be present on the page, so we decided on something like this:
While I am sure some may find it less than ideal, this choice was also weirdly elegant with the given constraints. Every major FW has a “global” (js)/”context object” (react)/”root scope” (angular) – so we could also have one. The important thing was to keep this interaction to a single point in code (a dedicated service).
One last thing though, we passed a data-bound object from a React component to an AngularJS directive, but what if we wanted to communicate back? Of course, if you have access to every AngularJS service you can communicate back messages to AngularJS, for instance $rootScope, $broadcast, etc.
To Sum It Up
The integration between the two FW’s is hard, given the big differences between them and their supporting tool sets. But we were able to embed one into the other and keep the migration “sane”, though that’s a relative term.