Migrating an Angular 1 Application to React
With Angular 2’s full release on the horizon, developers and teams will start having to think about whether to migrate their Angular 1 applications, and if so what to. This article focuses on React being the answer, providing a pragmatic, incremental approach of migrating a large single-page application from Angular 1 to React, allowing development to continue side-by-side in both technologies.
This article will first touch on some of the reasons you might want to move, and details the general concepts and application structure you’ll want to aim for, before an in-depth dive into the specifics of the migration. The final section addresses how to adjust to life using React, providing alternatives and equivalents to Angular’s built in functionality and associated libraries.
Why Not Angular?
Another point I’d like to stress is that you may not need to move at all — Angular 1 is established, stable, and I completely expect support to continue for a while yet, with it continuing to receive backported features and syntax from Angular 2. However, its shortcomings will never be fully addressed, but if you’re not experiencing these and don’t expect to develop your application to a point where you will be, don’t just move for the sake of it. Although, implementing some of the changes detailed in this article may give you some benefits, which will be explored in the coming sections.
Why Not Something Else?
There are an array of other options out there at present, from component libraries such as Vue.js, to frameworks such as Aurelia, to full-blown languages such as Elm. My recommendation would be to experiment with these technologies and learn from them, but if you’re making technology choices for an organisation where numerous developers will be working on the application over its lifetime, you should choose wisely based on the project’s stability, activity, ecosystem, community, learning-curve, skills within the job market, and ease-of-migration should things change.
On to the how…
Structuring Your Application
When Angular 1 was introduced, up until recently, the recommendation was to structure applications around controllers, whereby controllers were set up and associated with a given DOM element using ng-controller, with the controller’s properties and methods made available to elements contained within via the use of $scope. Directives, services and filters were also available, but ultimately, everything was built around controllers and their two-way bound $scope. The overwhelming majority of tutorials on the web follow this approach, so the information that’s out there can be confusing. While this was (and is) fine for simple applications, as front-end applications grew in complexity, this led to the scope soup problem amongst other issues. If this sounds like your current application, you may want to begin by reading Refactoring Angular Apps to Component Style to get some more background.
As the link above suggests, the recommended approach for front-end architectures nowadays is to build them as a tree of components, (named directives in Angular 1.4 and before, and components in Angular 1.5 onwards, with differences in syntax). Conceptually, there are generally two types of components, container components (sometimes referred to as containers, smart components or stateful components) and presentational components (sometimes referred to as dumb components, stateless components, pure components, UI components, or simply just components).
Container components generally reside at the upper-levels of a component tree and are either stateful, or interact with a state store (more later), and potentially interact with backend servers, Angular services and other libraries. If building a single-page application which has routes, these would usually be the entry points for the routes. They can be nested — for example you may have one main container that controls your navigation and header, with the content being loaded via routes using child container components, much like nested state views in angular-ui-router.
Presentational components are predominantly UI components which are passed in data and callbacks by container components, calling the callbacks with updated data when specified events occur. A couple of examples might be a button component with a label property (attribute) and an onClick callback property, or a range slider component with value, min, max and step properties and an onSlide callback property which passes back the new value. Usually, they do not contain any state, and it is good practice to avoid doing so wherever possible, having them simply render anything that is passed to them, which makes them predictable and easy-to-test. As with container components, they can be nested; any relevant data and callbacks are simply passed down the tree to sub-components.
For a more in-depth description of both types of components, I would recommend reading Presentational and Container Components. However, the concepts are relatively new, so expect to find a number of varying definitions (and names) around the web, and for them both to evolve over time.
If your application has routing, and therefore multiple container components that need to share data across the application, it can be a good idea to introduce a centralised state store. In Angular 1 applications, the common approach to sharing data across the application is to use services, but in complex applications, these can often become difficult to manage or unpredictable, especially when combined with Angular’s two-way binding. Popularised by Facebook’s Flux pattern, and inspired by Elm, a number of libraries have sprung up to assist in managing state, such as Alt, MobX and Redux. While Flux and other approaches are relatively new, there are likely to be innovations around state management, but at the time of writing this article, Redux is quickly breaking away from the pack to become the de-facto standard for it; it is lightweight, simple, has a solid ecosystem and community around it, is extremely well-documented, and has libraries for using it with both Angular 1 and React, which make it especially suited to this article.
Combining the concepts of container components, presentational components and a state store, a simple CMS application which contains /list and an /edit page may look like the following:
There is no two-way binding in this architecture; the data flows down from the state store to the presentational components, and back up again in the form of function calls with the new data passed as arguments.
Now the general concepts are out of the way, I will dive into the specifics, starting with presentational components.
The logical place to start with migrating the application are the presentational components, given they are intended to be small, simple and stateless. We will be using a library called ngReact which bridges the gap between Angular and React, allowing both to be used in conjunction by wrapping the React component in an ngReact Angular directive. The whole library weighs in at around 250 lines of code (including comments), so it is simple to gain an understanding of how it works, but that is mostly beyond the scope of this article.
I would recommend working upwards from the lowest presentational components in the component tree to the highest when migrating, given that ngReact does not currently support transclusion of Angular directives into React components. Below is an example of how you might migrate a simple Angular toggle directive, which allows you to toggle between a true and a false value with customisable labels for the values, to a React component wrapped in ngReact directive:
Converted to React using ngReact:
One thing you may notice about the above example is that the usage has now changed — instead of using @, and & bindings, all the bindings are now = bindings. This is how ngReact implements bindings for the directives it creates, and whilst this may seem worrying at first, this has a number of benefits. Firstly, it allows for consistency of properties — no longer do you have to dig through the implementation details of the directive to see which binding type to use, or even worse, what to name arguments passed to & function bindings. It also makes directives more testable — instead of testing the whole component tree to test function bindings, you can now test that a function has been passed into a sub-component as it will no longer be wrapped in a closure by Angular to retain access to the outer scope.
One thing to be careful of with = bindings though is to not set up unnecessary digest cycle watches, which can be avoided by the use of the one-time binding operator (::), as shown above with the onToggle function. Generally, this would be used for all callback functions, as well as for values you wouldn’t need to change, which in our example, would most likely be trueLabel and falseLabel.
A small gotcha to watch out for with the use of = bindings for everything is that where you’d usually pass in a string literal to a @ binding (e.g. trueLabel=”True”), you would need to instead wrap it in an additional set of quotes:
The final thing, and possibly the most important thing to note is that the React component will no longer update the value binding. This is a fundamental difference between React and Angular; React operates using a one-way data flow, whereas Angular was built around two-way data flow. Therefore it is up to a parent component to update the value and pass it back into the toggle component, with the idea being it makes the application more efficient, predictable and easier to trace updates, which especially holds true for more complex applications. You may be thinking that because ngReact makes use of two-way = bindings, that this will magically retain Angular’s two-way data flow, but this is not the case — internally it copies or reassigns the values. Again, this is not a bad thing, as it is following the more modern approach of one-way data flow.
Whilst the above differences may be easy to manage in simple applications with simple components, they may be seen as a deal-breaker for larger applications and complex components, especially when the components are covered by solid existing unit and/or integration tests or are reused between multiple applications. In this case, the best approach is to create an additional wrapper directive between the Angular application and the ngReact directive to maintain the current directive’s interface:
As the parent components are moved to React, the ngReact wrapper layer can be removed and the React component can be used directly. However, if you have existing tests, it may be an idea to use the ngReact wrapper in your tests to avoid rewriting the tests to test the React component directly, though the latter option would most likely yield faster and less complicated tests.
On the subject of tests, there are a couple of issues to watch out for. Firstly, ngReact makes use of $evalAsync internally to defer appending the React component to the DOM to the end of the current digest cycle. This means that when you compile an Angular directive which contains an ngReact directive within a test, the ngReact directive will not be compiled unless you flush the $timeout service. Secondly, React’s event system is significantly different to Angular’s; Angular binds native DOM events to the DOM nodes which they are set up on via the use of ng- prefixed events, whereas React has its own Synthetic Events, which attach event listeners to the root node and provide some additional cross-browser standardisation. To address these two issues, I created ngreact-test-utils:
ngreact-test-utils — Test utilities for Angular apps using React and ngreactgithub.com
Currently, the library contains two functions — compile() and simulate(). compile() encapsulates all of the usual bootstrapping that is required to set up Angular directives for testing, in addition to flushing the $timeout service. simulate() is inspired by React’s official test utils and fires both native Angular events and React Synthetic events. Both functions are suitable to use with Angular-only directives, allowing for the existing tests to be used as-is when the directive is migrated to React using ngReact. Further information can be found in the project’s readme.
For testing React components directly, I would recommend using AirBnB’s Enzyme library, which builds on top of React’s official test utilities to provide a much nicer API. Enzyme has been that well received that Facebook are discussing using it as their offical test utilities.
If you are not ready to commit fully to using ngReact + React yet, or if you’d like to continue developing Angular directives whilst migrating, the following pointers will put you in good stead for a future migration to React using ngReact:
- Use = bindings for all directive attributes
As explained above, ngReact uses = bindings for its directives, so this would allow for existing directive interfaces to be carried across without the need for a separate ngReact wrapper directive. Be sure to use one-time binding (::) syntax where appropriate.
- Avoid using two-way bindings
While this may seem contradictory to the above point, it means that whilst you are using two-way = bindings for passing data to your directives, you should keep them strictly one-way and not actually make use of them to update data. Get into the practice of passing the data and callbacks into the component and then updating the data via calls to the callbacks with the updated data as arguments. Depending on your usage and the data type, you may find that within the directive, you may need to either make a copy of or reassign the value to avoid updating the two-way binding, though when the directive uses ngReact, ngReact will not allow any two-way binding and data can only flow back out via callbacks.
- Avoid attribute directives
- Avoid transclusion
While React supports transclusion of child components (and additionally transclusion via properties), ngReact does not support transcluding Angular directives into React components. This is definitely not a bad thing, as transcluded Angular HTML would likely be extremely inefficient, circumventing the virtual DOM, and discussions to bring such a feature into ngReact are yet to be successful. As an alternative, favour passing in the data via attributes, for example if you are building a navigation component, do not transclude in each navigation item component, but instead pass in an array of navigation items via an attribute and render them internally in the navigation component.
- Avoid dependencies
Styling Presentational Components
For the purposes of this article, I’d recommend sticking with CSS combined with a solid CSS methodology such as BEM to style components, allowing existing styles to be re-used between both React and Angular components, giving you one less thing to worry about. For React, I created a very simple BEM helper function for people who may miss ng-class and hate the mess and repetitiveness that BEM creates:
The next logical step is to split out our state into a separate state store. As mentioned in the structural overview section, we will use Redux for our state management. If your application is not complex and you’ve decided that a state store is not for you, feel free to skip ahead to the container components section. If you’re undecided, the Redux FAQ is most likely be the best place to start. However, using Redux in the context of migrating from Angular to React may ease the process, as you now have your application state in one place and that can give you one less thing to worry about when moving components around.
If you are unfamiliar with Redux, there are a great deal of resources out there which explain the concepts much better than I could myself. The best ones are the official documentation, or if you prefer videos, Dan Abramov, the creator of Redux (and now a Facebook employee working on React), has recorded an excellent Egghead.io video series on the subject.
To further aid the migration, you should move all of your HTTP calls to Redux actions. For this, there is a range of middleware you can use, such as Redux Thunk, Redux Promise, Redux Saga, or you could even roll your own middleware based on your specific needs. I’d recommend trying each out and figuring what works best for you, and again, the excellent Redux documentation has sections devoted to async, async actions and async flow, to help you out. For the actual HTTP calls themselves, as we’re now dealing with plain JS, you’ll want to use a library that’s not part of Angular, such as fetch, superagent or axios. More details can be found in the final section of this article, which looks at libraries to use.
Once you have set up your reducers and actions in Redux, you can create a store to use with Angular using ng-redux. ng-redux is configured via a provider, allowing you to create the store from the reducers and any middleware you need, which can then be injected into any container component which requires access to the store via $ngRedux. Further details can be found in the ng-redux documentation.
Though usually overkill, another aspect of your application you may want to move to be managed by Redux is your routing. In terms of the migration, doing this allows you to avoid injecting $state (angular-ui-router) or $location (ngRoute) into your ngReact container components to control the application’s routing, which would need to be rewritten when the application’s routing is switched over React. The integration with Redux can be achieved with redux-ui-router for angular-ui-router or ng-redux-router for ngRoute. To prevent having to change code when routing is migrated to React, you would want to create a set of actions which mirror the react-redux-router actions, but wrap the ng-specific actions. For example, if you’re using ngRoute:
Once your routing is switched to React, you can then use the react-redux-router actions directly. Whilst this is very simple if you’re using ngRoute, given both ng-redux-router and react-redux-router use URLs directly, and have very simple APIs, this becomes quite a bit more complicated if you’re hoping to migrate from redux-ui-router, given that it abstracts away from URLs by using features such as named states and objects for query string parameters. This means you would have to implement logic yourself to translate URLs to angular-ui-router conventions:
You could also do this the opposite way around if you prefer the higher-level API angular-ui-router provides and want to carry this over to React, but depending on the level of features you make use of in angular-ui-router, the action wrappers could become pretty complex, in which case it might be better just to take the hit and refactor your containers to use react-redux-router actions instead when you switch routing over to React.
Another thing to consider related to routing is the retrieval of query string parameters. Given the stores are different between the Angular and React router reducers, you may just want to consider extracting parameters directly from window.location, though this may be overkill given you could just update the store mappings in the container components.
- Avoid providers — these are an Angular specific construct and wouldn’t be very nice to use outside of Angular. Instead, use services at the cost of allowing configuration properties to be set outside Angular config blocks
- Think carefully about how you architect your services — all services, factories and providers in Angular are singletons; even if you write them as factory functions or ES6 classes, they will be the same instance wherever they are injected. When moved out of Angular, you’re opening them up to be consumed differently unless you build a separate layer on top.
As for the container components themselves, the strategy here will be to maintain the application bootstrapping and routing in Angular, then have the routes/states interface with ngReact container components. Where you may have contained logic in your route/state controllers in your existing application and set up complex templates, the key here is to move this logic into React and keep the routing layer as thin as possible, simply rendering the ngReact container component as the template:
Connecting the React container components to the existing $ngRedux store is very straightforward; it is simply a case of injecting it into the ngReact directive and then passing it along as store via reactDirective’s 4th argument (used to inject additional properties):
For the React container component itself, you would use connect from react-redux to extract the state you need from the store for the container. A guide to its usage can be found in the Redux documentation and this code would not need to change once the application is migrated fully to React.
Once all containers have been migrated, you can remove the thin routing layer and bootstrap your application using React, react-redux, react-router and react-router-redux, therefore completing your (hopefully) relatively pain-free migration!
You Might Not Need Angular
As with moving between any technologies, it can be hard to adjust and let go of the previous one’s functionality and conventions. This was true a few years ago of developers moving from jQuery to Angular for the first time, dropping DOM manipulation in favour of two-way data bindings, and is now occurring with developers moving from Angular to React. This doesn’t just involve dropping two-way bindings in favour of one-way data flow and state stores, but also involves the need to replace built-in features of the framework with modular libraries designed for specific tasks.
In this section I will do my best to address this, by offering some alternatives and equivalents, and while it may seem overwhelming to have to make such choices, I hope to prove to you that this is not a bad thing, and surprise you with how little you may actually need to replace.
Probably the most common service to use in Angular applications. Here you have a few choices: fetch (by GitHub) would be the logical choice, given it is a polyfill for a feature that is soon to become native across all major browsers. Though if you like Angular’s $http service, which is definitely one of its good parts, Axios mirrors a lot of its API, and there is also the popular superagent.
The purpose of these with Angular was to hook into the digest cycle, which is obviously not needed with React, so you can just use the native equivalents.
As above, you can use the native objects (sparingly — see angular.element).
This is one piece of Angular magic I never enjoyed working with, but was often a necessity due to the two-way binding. React’s one-way data flow makes this much simpler by allowing you to hook into it to parse and validate input on change, then format it for display in the input when it is passed back into the component via props. Where ng-form would store the state of a given form, this can now be handled by Redux.
Working with the DOM is slow, and should be avoided wherever possible in React, but react-dom is available for when it can’t be. You could also still use jQuery with React (or any other DOM library for that matter), but I would strongly recommend against it.
- angular.forEach, angular.extend, angular.merge…
As above, but with .map()
Unlike Angular which has two popular routers, there is only really one with React — react-router, which has recently joined forces with its closest competition
Whilst it is still possible to use Protractor with React and maintain your existing tests, it contains functionality specific to Angular that you won’t need, so consider alternatives such as Nightwatch.js. However, given React makes testing a lot simpler than Angular based on its component approach and the excellent testing library Enzyme, I would recommend taking advantage of this, using unit tests to test your presentational components and integration tests to test your containers, relying less on slow, brittle and complicated end-to-end tests.
See dependency injection below
- Dependency injection
- Angular Material
Not technically a part of Angular, but closely associated with it, Angular Material can be replaced with material-ui, which contains the majority of the components that Angular Material has, and a few additional ones, and although it is a little less polished and a bit more buggy, it allows for components to be imported individually, and includes all of the material icons as easy-to-use React components.
Facebook offer react-addons-css-transition-group, which is based on ng-translate
Migrating any application can be a daunting task, but hopefully this article has provided you with the necessary tools and information to carry out a well-executed, low-risk migration, allowing business to continue as usual. Even if you do not make it the whole way through the application, migrating some, or all, of the presentational components is still a major win in terms of performance, by reducing the number of Angular bindings, testability, and a general reduction of code. This comes at the relatively small cost of having to load React, which weighs in at around 40kb minified and gzipped, and ngReact, which adds an additional 2kb. If you’ve gone an extra step and moved your state to a Redux store, you will benefit from reduced complexity, less debugging headaches, and likely greater application stability overall.