React JS Injection into Angular JS Application or Fighting for Performance Increase






4.57/5 (8 votes)
React JS Injection into Angular JS Application or Fighting for Performance Increase
Introduction
It won’t be a discovery to say that there are hundreds of plugins and libraries, which simplify the building of modern web interfaces. One of them is AngularJS.
There are many articles about its performance; most of them are focused on what not to do for things to be good. It’s considered that only incorrect apps are slow, and the correct ones shouldn’t contain more than 2000-3000 DOM elements. If they contain more, then something is wrong. While this statement is quite valid, sometimes you have to write a “wrong” web app due to requirements that you have. In this article, we’d like to tell about one of such tasks and how it was solved. We believe this article will be useful for professional web developers.
So, our task was to develop the calendar for the sports club booking system. The calendar represents seven 12-hour blocks - let’s call them Days. Every Day is split into 15-minutes intervals (see screenshot below); each interval (cell), in turn, may contain from 2 to 10 DOM elements. So far so good, the peak number of elements is about 3000.
Well, let’s get to work:
<div ng-repeat=”day in days”>
….
<div ng-repeat=”hour in day.hours”>
<div ng-repeat=”block in hour.blocks”>
…
<div ng-repeat=”block in hour.blocks”>
…
<div ng-repeat=”session in block.sessions”>
…
</div>
</div>
</div>
</div>
</div>
Done! But! The data binding for all of these elements took around 2-3 seconds. By the way, here comes a question: how to measure how much the data binding takes in general? The attempt to do it using profiler doesn’t answer the question as it’s hard to distinguish what falls into the data binding, what is added by routing and so on.
Therefore, we made it easier the following way:
$timeout(function() {
$scope.days = days;
var now = new Date();
$timeout(function() {
console.log(new Date - now);
$scope.apply()
}, 0);
}, 100);
The first timeout is necessary for all other scripts to finish and not to violate the measurement accuracy; the number 100(ms) is picked by experiment. The second timeout can be set to 0 because as soon as the browser gets a chance it will instantly perform the internal handler of $timeout
, which will indicate the end of the data binding.
2-3 seconds are quite large numbers from the UI responsiveness point of view, so how can we reduce the time?
First and foremost, it’s worth rereading this: tech.small-improvements.com/2013/09/10/angularjs-performance-with-large-lists/ and, certainly this: Optimizing AngularJS: 1200ms to 35ms. The latter article, by the way, shows a particularly impressive result. However, the result is achieved through virtualization (i.e. only the visible part is being rendered) and caching. Virtualization is a good solution but only when the content is not too complicated. Otherwise, the unpleasant lag occurs while scrolling the page, which neutralizes all the virtualization positive effect.
Speaking about virtualization: there is a great AngularJS module called ngGrid
that can display millions (check it out?!) of elements. But the secret behind it is the virtualization again (otherwise, why all the rows in the ngGrid
have the same height?).
Eventually, through trial and error we come to ReactJS. Developers of the library claim it is the fastest framework for data binding, and now we tend to agree with it. Despite the opposite opinion expressed at the Vue website, our own test showed the advantage of React.
Now React.
There are many articles about React framework already, like these ones:
- http://maketea.co.uk/2014/03/05/building-robust-web-apps-with-react-part-1.html
- http://maketea.co.uk/2014/04/07/building-robust-web-apps-with-react-part-2.html
- http://maketea.co.uk/2014/05/22/building-robust-web-apps-with-react-part-3.html
- http://maketea.co.uk/2014/06/30/building-robust-web-apps-with-react-part-4.html
The secret of ReactJS is that it operates the virtual DOM; therefore it can minimize the number of actions necessary for rendering the View.
The code for rendering the View in React looks like this:
var Hello = React.createClass({
render: function() {
return React.DOM.div({}, 'Hello ' + this.props.name);
}
});
React.renderComponent(Hello({name: 'World'}), document.body);
The first part here defines the component, which is then rendered by the line:
“React.renderComponent(Hello({name: 'World'}), document.body)”
API React.DOM
is relatively convenient. However, in comparison to Angular and Knockout templates, the DOM generation through JavaScript looks a bit outdated and can seem a rather time-consuming task. The Facebook offers “In-browser JSX Transform” to solve that problem.
When the Transformer meets
<script type="text/jsx">
in browser, it converts this code
var HelloMessage = React.createClass({
render: function() {
return <div>{'Hello ' + this.props.name}</div>;
}
});
into this:
var Hello = React.createClass({
render: function() {
return React.DOM.div({}, 'Hello ' + this.props.name);
}
});
Note the syntax of return [Html markup];, it goes without quotation marks (however, from the experience with ReactJS.Net, extra parenthesis or lines won’t hurt). To display elements of an array, you need to do the following:
var HelloMessage = React.createClass({
render: function() {
…
var nodes = array.map(function(element) {
return <div>…</div>;
});
return <div>{nodes}</div>;
}
});
Although, most likely, you will do this:
var SubMessage = React.createClass({
render: function() {
return <div>…</div>;
}
});
var HelloMessage = React.createClass({
render: function() {
…
var nodes = array.map(function(element) {
return <SubMessage >…</ SubMessage >;
});
return <div>{nodes}</div>;
}
});
Why? The answer lays in the fact that the components need to have their own States described below.
By the way, the JSX transformation can be delegated to the server; there is a wide range of integrations: https://github.com/facebook/react/wiki/Complementary-Tools#jsx-integrations. It’s exactly what we did, and in our case it was ReactJS.Net.
Ok, what about the two-way binding, a cornerstone of MV-* frameworks? As it is shown in the article http://maketea.co.uk/2014/03/05/building-robust-web-apps-with-react-part-1.html, ReactJS doesn’t appear as such, but there are some solutions for the problem. The properties and states of the component differ in ReactJS. Properties are something that we pass to the component by calling React.renderComponent(Hello({[PROPERTIES]}), …)
and receive inside the component using the this.props. In most cases, Properties are the object tree used to generate HTML markup.
Ideologically, Properties suppose a rather static behavior. To refresh the markup whenever the Properties have been changed, we have to call React.renderComponent
. Speaking from experience, however, the renderComponent
often works faster than similar constructions of MV-* frameworks as the algorithm of searching for differences is good enough.
A State
is something that can change. There are 2 main methods to deal with the State
:
getInitialState
– returns the initialState
of the component (that will be returned based on the Properties).setState
– sets theState
.
When you call the setState
of a particular component, only that component will be re-rendered.
How to push data back? This requires a valueLink
object that presents an initial value and a changes handler. This handler has to act according to the situation. The Facebook doesn’t recommend pushing the new value right into the props; therefore we have to set the State
to the new value in order to refresh the markup according to the data. In other words, the two-way binding in ReactJS will resemble the following:
What in Angular was:
<input ng-model=value />.
in ReactJS will be:
var HelloMessage = React.createClass({
getInitialState() {
return { value: this.props.value };
},
render: function() {
var valueLink = {
value: this.state.value,
requestChange: function(newValue) {
this.setState({value: newValue});
}.bind() // Pay attention to the call of .bind()
};
return <input value={valueLink}/>;
}
});
Too long? That's right! This is the price we pay for the five times performance increase.
So, back to our task, here is what we have:
- AngularJS application that contains slowly working part;
- We want to increase the performance (actually, five times increase works just fine);
- We want to achieve the above with minimal effort;
- Ideally, we want to modify only the View part of the app and keep the Controller and the Model unchanged.
The idea is described at http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/ and at http://davidchang.github.io/ngReact/.
Therefore, our:
<div ng-repeat=”day in days”>
….
</div>
converts into this:
<div calendar=”days” />
This is not React yet - it is still Angular; the calendar is a directive that tracks down changes in the Days and calls the renderComponent
.
The good news is: the View code can be changed into JSX almost mechanically. Ng-class converts to className={className}, where className represents just calculated names of the classes (please, make sure it is exactly className and not class; if you mix them up accidentally, React will give you prompts in console). Ng-show converts into style={style}. Ng-Model becomes something shown above (see the HelloMessage component listed above). The process is so straightforward that there is even ngReact library that does it automatically. However, as it is shown here, there might be an issue with performance. To avoid this, we chose a proved “manual” way.
As A Result
What was taking 2-3 seconds before, now takes 300-500 ms. This makes user experience very pleasant. The rework process took us about 3 days while the initial realization – around 15 days. We managed to suffice with just the change of the View, but maybe it was a specific of this particular task.
In conclusion, it is worth saying that using React “injections” (if you allow us to call them this way) in the Angular apps allow us to increase performance in five times. Most importantly, despite the fact that modifications are mostly manual and mechanical, they are being applied locally. We believe that, in similar cases, React can be a kind of “magic wand” speeding up the “slow” parts of the app.