We’ve been renewing our front end stack lately. Joel, a software engineer at Smartly.io tells how we migrated from Angular to React without a massive rewrite.
@jsterlibs: How to Migrate from #angularjs to #reactjs Without a Massive Rewrite
The user interface of Smartly.io’s SaaS application was started as an Angular-based single page application back in 2013. We chose Angular, because it was an all-in-one solution and it had a seemingly straightforward data flow, which allowed rapid development and experimentation. Lately, we’ve been seeking ways to renew our front end stack without compromising our development speed or customer satisfaction.
Smartly.io’s user interface consists of hundreds of components used to manage and optimize various advertising campaigns on Facebook and Instagram. As the product evolved, we added new tools into the mix to ease the build process and keep our development pace up.
Previously, we moved from Grunt to Webpack which allowed us to use eg. ES6 features in our daily work. One of the main concerns of Angular still remained: The state of the UI was spread out to multiple services, directives’ models and such. Using Redux with ngRedux seemed like the correct solution, so we adopted it.
Angular’s relative complexity, rendering performance on large listing views and overall developer happiness were still issues. Many developers at Smartly.io had good previous experiences with React and it was also more mature than Angular 2 at that time. That’s why we chose React as the front end’s component library.
Migrating one component at a time
We had to choose to either rewrite the whole front end application, or go through the migration more gradually. As the first option was too risky and it would’ve stopped us from developing new features for months, we chose the gradual approach.
We wanted to access the Redux state in the new React components, but ngRedux didn’t allow this directly. Our developer Ville made a fix to ngReact which allowed us to create a more general solution to migrate new React components to an existing Angular app without having to rewrite the whole application. This way, the migration started from the outermost leaves of the component tree, and then slowly but surely spread React through the entire application.
Generalized bridge binds ngRedux’s state to React
The core of the solution is a generalized bridge component, which allowed us to wrap React components to be used also in the Angular application. The component is based on the excellent reactDirective from ngReact, which handles the basic interactions with Angular, like taking care of the component’s lifecycle, and re-rendering React components when the passed parameters change.
Our bridge service also adds the state from ngRedux to the React component by default, which reduces repetition a bit. The whole bridge code is as follows:
A common use case for our users is to find campaigns based on certain properties, such as “has predictive budget allocation enabled” or “has automated post boosting enabled ”. Originally, we used an Angular component called CampaignLabels to show the labels.
After porting it to React, we still had to render it in the campaign management view. Unfortunately, the view was still implemented in Angular, so we wrapped the React component in the following way:
Which is then used in the Angular template in CampaignCtrl :
The wrapped component looks and behaves like a native Angular component. It even replicates one of the more unfortunate things, as it does not complain about missing required properties. Fortunately, the new React components are typed with Flow, which helps in catching missing or incorrectly formatted properties when used directly with other React components.
A big part of the transition was to create a component gallery, which would function as a style guide. Usually, before starting to write new components, the developers scroll through the style guide’s list of components, and see if they could use some of the existing ones instead of creating new ones from scratch. This saved time and effort especially in the beginning of the migration, when there were lots of new components coming in.
Today, more than 41 % of our components are implemented with React and almost all of them have Flow typing enabled. We’re currently replacing the components at the core of the workflow, which will dramatically improve the rendering performance, and also allow us to deprecate a huge number of Angular components. After implementing the base components first, such as forms and interactions, we’ll be able to transform the rest of the application a lot faster.
However, we have to note, that the comparison is not completely apples to apples. The Angular components were designed with a lot broader responsibility than the React components. Therefore, 41 % of components does not equal 41 % of the functionality.
How could we have done this even better?
Our motto for changing the codebase has been “New code? React. Refactoring old code? React maybe “. Our new test setup allows rendering components without the DOM which is a lot faster than always rendering them in the browser, making writing tests a lot easier and nicer. React’s one-way data flow feels more native to all our developers, which has made the migration smoother. Easier testing and the simplicity of the framework had a huge positive impact on the speed of the migration.
In hindsight, we could have paid more attention to these details to make the migration even faster.
All in all, we’re happy with how the migration has made rendering faster and creating new components simpler—and our users have noticed the results. We’re aiming to finish the migration during the first half of 2017, which should allow us to simplify our frontend library stack nicely.