Last month turned out quite an exciting one. I got to spend a day with our React development team. They were starting on a new project. It was AngularJS to React migration. I learned various practicalities on which projects are taken, learned the best practices, approaches to technology migration and how decisions are taken. But there is only so much you can learn about a project in a single day when the team is going to spend many coming months into development.
While I enjoyed working with them as a team member, I couldn’t take my mind off the project. Somehow, I wanted to be more invested in the project. I got a warning from the team, “things are going to get more technical from here.” I persisted. I became more involved into the project. The blog is a reminiscence of my time at our React lab and active sessions with the developers.
What exactly is a React component? It is yet another UI component. And what does a UI component does? It acquires data, triggers actions, and renders them to the DOM. In addition, a UI component maintains the internal state and handles user interaction. So what could be a simple concept of a bridge to connect ‘old’ world with the ‘new’ world? An AngularJS component acting as a thin client with the obligation to litate data to the React Component.
The main Application component is wired up with complex logic like routing. The routing is deeply integrated with the main components and a key piece of AngularJS. NavItem is friendlier. It displays a link, has some trivial logic like “am I ready” and displays anchor text.
The content part of our app contains sub tree showing a list of comments. The ComponentList is complex, as it is attached to the data layer and may retain state: what item is selected etc. The Comment is the easiest part of that tree. It essentially renders a Comment and handles user interaction. The Text component for example is responsible for rendering the text. That component will render in another technology without hiccups.
This is how AngularJS comment component looks like
module.exports = angular.module('ngReactExample.comment', [ ]).component('comment', { bindings: { comment: '<', }, template: '{{ $ctrl.comment.text }}', controller: function() { } });
And this is the version of ‘comment’ in React
const Comment = (props) => { return ( { props.comment.text } ); }; export default Comment;
As I said, the React component is a UI component that acquires data, triggers actions, renders them to the DOM.
ReactDOM.render(<Comment />, element);
Let’s try to call this within an AngularJS component:
import Comment from './Comment'; module.exports = angular.module('ngReactExample.comment', [ ]).component('comment', { bindings: { comment: '<', }, controller: function() { ReactDOM.render(<Comment />, $element[0]); } });
It did not go as smooth as the final piece of code makes it look like. But it gave the a simple yet influential starting point from where you can build the AngularJS – React bridge or the bridge to the old and the new world.
In fact, we used AngularJS to our advantage. We did not have to get into DOM node ids or employ DOM API to query the element we need to render React to when we can pass references to the AngularJS element.
If you’re developers you might have noticed the subtle details–we’re rendering React component only when this component is primed to begin with. But what about when we want dynamic element. In an ideal app, component changes must triggers rendering. There is a lifecycle method called $onChanges.
import Comment from './Comment'; const render = (element) => { ReactDOM.render( <Comment />, element ); } module.exports = angular.module('ngReactExample.comment', [ ]).component('comment', { bindings: { comment: '<', }, controller: function($element) { const $ctrl = this; $ctrl.$onChanges = () => render($element[0]); } });
Evidently, changes to AngularJS component redraw the React component.
In React, props acts as an interface to send data to a component. Did you know AngularJS directives receive inputs via bindings? Only workaround was to push the comment data to an outside component. From where, we pulled it down to our React component. Presenting you the first working prototype of the AngularJS-React bridge.
import Comment from './presenter'; const render = (element, props) => { ReactDOM.render( <Comment { ...props } />, element ); } module.exports = angular.module('ngReactExample.comment', [ ]).component('comment', { bindings: { comment: '<', }, controller: function($element) { const $ctrl = this; $ctrl.$onChanges = () => render($element[0], { comment: $ctrl.comment }); } });
Being a JavaScript core-React won’t clean up the components itself. A potential memory leak might happen. Fortunately, lifecycle hook $onDestroy() of house AngularJS can dethrone the React component.
import Comment from './presenter'; const render = (element, props) => { ReactDOM.render( <Comment { ...props } />, element ); } module.exports = angular.module('ngReactExample.comment', [ ]).component('comment', { bindings: { comment: '<', }, controller: function($element) { const $ctrl = this; $ctrl.$onChanges = () => render($element[0], { comment: $ctrl.comment }); $ctrl.$onDestroy = () => ReactDOM.unmountComponentAtNode($element[0]); } });
Who says passing data from AngularJS to a React component is complicated?
Not a eureka moment but we could wrap a React component with an AngularJS layer with the ingenious piece of code. We can reuse the code in the rest of the application.
What a starting point and a proof of concept. I can’t wait to work with the team again although they are raising eyebrows.