📜 ⬆️ ⬇️

Injecting React JS into an Angular JS app or fighting for performance

Dear Hub lovers, hello everyone! Do not open America if we say that there are hundreds of plug-ins and libraries that facilitate specialized tasks associated with the construction of modern web interfaces. Angular is one of them, a lot has been written about its performance, and in most cases recommendations are given which do not need to be done so that everything is good.

The main argument of the supporters is that the wrong applications are slowly running, but the right ones should not contain more than 2000-3000 elements. If it contains more, something is wrong. See for example http://iantonov.me/page/angularjs-osnovy .

The argument is generally sound, but there are always situations where you need to write a “wrong” web application because there are such requirements. In this article we decided to tell just about such a problem, and how we solved it. In our opinion, the article will be more useful to professional web developers. So, our task was to make a calendar for the reservation system for one sports club. The calendar displays seven 12-hour blocks, each day of which is divided into 15 minute intervals. A block can have from 2 to 10 DOM elements. It seems nothing foreshadows trouble, the upper limit of ~ 3000.

')


Well, we're starting to write:
<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 of all this took about 2-3 seconds. By the way, there may be a question, how to measure how much it takes? Attempting to do this with the help of the profiler does not directly give an answer, it is difficult to understand in the profiler what exactly the data-binding sheet refers to, and what routing adds and so on.

Therefore, we made quite simple:
 $timeout(function() { $scope.days = days; var now = new Date(); $timeout(function() { console.log(new Date - now); $scope.apply() }, 0); }, 100); 


The first timeout is needed in order to run all the other scripts in order not to violate the measurement accuracy, the actual number 100 (ms) is chosen experimentally. The second can be safely done 0, because as soon as the browser receives the first breather, it will immediately execute the internal handler $ timeout, which in fact means that the data-binding is complete.

In terms of responsiveness of the user interface 2 - 3 seconds are quite large values, how to solve this problem?

First of all, of course, again and again re-read tech.small-improvements.com/2013/09/10/angularjs-performance-with-large-lists and, of course, Optimizing AngularJS: 1200ms to 35ms . In the last article, the result, by the way, looks particularly impressive. But the result is achieved by virtualization (i.e. by the fact that only the small displayed part is rendered) and caching. Virtualization is a good solution, but only when the content being virtualized is fairly simple. Otherwise, there are very unpleasant brakes when scrolling the page, which level the entire effect of virtualization.

By the way, speaking of virtualization, there is a remarkable ngGrid element for displaying tabular data, which can display millions (check :)) elements. But the solution here, of course, is again virtualization (and otherwise, why all the lines in the ngGrid are the same height).

So, having tried many options, we finally come to ReactJS. According to the developers, this is the fastest framework for data-binding (and now I tend to agree that this is the case). Although, for example, the Vue site claims otherwise, my own test showed the superiority of React.

So react.

There are already quite a few articles on Habré devoted to this framework. For starters, I would advise:



as well as a great article.


On Habré there is its translation into Russian.

The secret of ReactJS is that it operates with a virtual DOM, so it can minimize the number of actions needed to render the view. Nice article about it here .

The code for displaying a view on ReactJS is as follows:
 var Hello = React.createClass({ render: function() { return React.DOM.div({}, 'Hello ' + this.props.name); } }); React.renderComponent(Hello({name: 'World'}), document.body); 


Here, the first part defines the component, and the line “React.renderComponent (Hello ({name: 'World'}), document.body)” draws it.
The React.DOM API is quite convenient, but after the Angualr and Knockout templates, DOM generation with Javascript looks a bit archaic and may seem too time consuming. To solve this, Facebook offers an “In-browser JSX Transform”.

Meeting in the browser
 <script type="text/jsx"> 


transformer will transform such code
 var HelloMessage = React.createClass({ render: function() { return <div>{'Hello ' + this.props.name}</div>; } }); 


in this:
 var Hello = React.createClass({ render: function() { return React.DOM.div({}, 'Hello ' + this.props.name); } }); 


Pay attention to the syntax: return [Markup]; without quotes (although from experience with ReactJS.Net, extra brackets or line breaks do not interfere). In order to display duplicate elements, for some array you need to do this
 var HelloMessage = React.createClass({ render: function() { … var nodes = array.map(function(element) { return <div>…</div>; }); return <div>{nodes}</div>; } }); 


Although more likely that you do like 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 is that the components have their own state, which is written below.

By the way, the JSX transformation work is quite possible to get to the server, for this there is a rich set of integrations https://github.com/facebook/react/wiki/Complementary-Tools#jsx-integrations , which we actually did, in our case it was ReactJS.Net.

And what, two-way binding - the cornerstone of the MV- * framework? Actually, as aptly noted in http://maketea.co.uk/2014/03/05/building-robust-web-apps-with-react-part-1.html , ReactJS is not, but some tools , to resolve such issues there. In ReactJS, the properties and state of a component are distinguished. Properties are what we pass to the component by calling React.renderComponent (Hello ({[PROPERTIES]}), ...) and we get it already inside the component through this.props. In most cases, properties are a certain tree of objects for which you need to generate markup. Ideologically, the properties suggest a rather static behavior. In order for the markup to change when updating properties, you will have to call React.renderComponent again (although I note from myself that this renderComponent often works faster than similar designs of MV- * frameworks, yet the difference algorithm is very good).

A state is something that can change. The state is determined by a pair of methods:


When you call setState, a redraw will only happen for the component for which it was called.

How to transfer the data in the opposite direction? To do this, you need a valueLink object, which is a starting value and a handler for changing it. This handler should behave according to the situation. Facebook does not recommend recording the new value directly in props, therefore, you will have to write the new value in the state in order for the markup to be updated adequately to the data. That is, in its most complete form, the similarity of two-way binding in ReactJS will look like this:

Where 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() //  ,   .bind() }; return <input value={valueLink}/>; } }); 


Verbose? Of course!!! This is the price for the five times increased performance.

So, returning to our task, what we have:
  1. The application on AngularJS in which a certain piece works slowly.
  2. We want to increase productivity (actually 5 times we will be acceptable).
  3. We want to achieve this with minimal effort.
  4. Ideally, we want to change only the View part of the application, leave both the Controller and the Model unchanged.


The idea is described in http://habrahabr.ru/post/215607/ .

Actually, our:
 <div ng-repeat=”day in days”> …. </div> 


turns now into:
  <div calendar=”days” /> 

But this is not React yet, this is still Angular, and calendar is a directive that tracks changes in Days and calls renderComponent.

The good news is that rewriting the View code on JSX is almost a mechanical process. Ng-class turns into className = {className}, where className is just the computed class names (by the way, notice what className is and not class, and if you forget it, React will helpfully tell you in the console). Ng-show in style = {style}. Ng-Model, see above. It is so mechanical that there is even a ngReact initiative that does this automatically.

However, as written here , you can get a minus to performance. In order to avoid, we went in a proven “manual” way.

Eventually:
What used to take 2-3 seconds, began to occupy 300-500 ms. This made the user experience quite enjoyable. By the time the alteration took about 3 days, the initial implementation of ~ 15 days. It was possible to manage to change only the view, although perhaps this is the specificity of this particular task.

As a conclusion, I would like to say that the use of React-injections (with your permission, let's call them that way) in applications written in Angular make it possible to increase productivity by about 5 times. And most importantly, the changes that are made, though they are manual and mechanical, are applied locally.

From our point of view, in similar situations React can become a magic wand that will speed up the “slow” places of the application.

Source: https://habr.com/ru/post/232229/


All Articles