Engineering

Better Together: Using Two Frameworks to Build Marketing Technology

AngularJS has been a major game-changer for application development at Turn. A fully featured JavaScript MVC framework, Angular allowed us to build the redesigned Turn Console quickly and efficiently -- and power better, faster marketing for our customers.

One of Angular’s most convenient features is two-way data binding. When the model changes value, it is automatically reflected in the UI, and vice versa. For the average web application developer using a framework like Angular for the first time, this seems like pure magic and is a huge time saver.

However, as with most things, the ease of use and convenience comes at a cost. The magic behind Angular’s automatic data binding is powered by watchers. Angular creates a watcher for every model that is bound to UI. During the digest cycle, Angular will go through the list of watchers one by one and check if the value being watched has changed. This is called dirty checking. As the list of watchers grows longer, performance decreases.

The Turn Console empowers marketers to efficiently manage their campaigns and view important insights. Naturally, as a result, some of our pages are very data intensive, such as our list pages. Our list pages enable users to dive into their campaigns and view performance metrics and metadata. A single list page could have as many as 200 rows and 48 columns, meaning there could be 9,600 cells displaying data and therefore at least 9,600 watchers. We initially implemented the list page purely in Angular, but such a complex data intensive component struggled on initial load and prevented the user from interacting with the page while it rendered the data. As one of the primary components that our users interact with on a daily basis, it was critical that the list pages rendered quickly and responded effortlessly to user interaction. We knew we had to come up with a better solution.

Enter ReactJS. ReactJS is a relatively new open source Javascript library that has recently risen in popularity. It is not a complete application framework like Angular is, but instead, provides developers with an efficient way of rendering views. Unlike Angular, React uses one-way data binding and requires you to explicitly “set the state” of a component to indicate a value has been updated. While two-way data binding is highly convenient for developing our application, it is not necessary in the list pages since the data shown generally does not change -- it is fetched from the server once on initial load. In this case, we would rather give up the convenience of two-way data binding for performance.

The advantage of this trade-off is that we no longer have to iterate through long lists of watchers to check their state. Instead of dirty checking, React utilizes something called the virtual Document Object Model, or virtual DOM, which is a lightweight in-memory version of the actual DOM. When a component has been updated, React:

  1. Creates a new virtual DOM.
  2. Calculates the difference between the newly created virtual DOM and the original virtual DOM.
  3. Re-renders the parts of the actual DOM that need to be updated using efficient batching and sub-tree rendering operations.

This process is much faster than updating the DOM directly and more importantly, it did not create a watcher for every cell element. React empowered us to have complete control over event listeners. Rather than deciding for us, we could explicitly add them whenever we deemed necessary.

React was exactly what we needed to speed up the performance of the list pages. Since React provides only the “View” functionality, it is agnostic of the underlying JavaScript framework, enabling it to work nicely with Angular. React could essentially replace what Angular is not very good at -- rendering a large set of data. We decided to implement a fused solution of having Angular and React work side by side in a generic modular component that we could reuse across our entire console, which we named Turntable. This allowed us to simultaneously take advantage of React’s efficient rendering and Angular’s comprehensive framework, while avoiding rewriting our entire codebase. The bulk of the list page would be rendered using React -- in particular, the rows, columns, header, and row toolbar. The rest of the page and the logic for fetching and manipulating the data on the list page remained in Angular. Turntable was exactly the holy grail we were looking for. The rendering time for 100 rows dropped by 91% -- from roughly 20 seconds to a mere 1.8 seconds.

With React and Angular being two of the hottest tools for web applications today, many developers often wonder which one they should use. We say, “Why not both?” Sometimes, you don’t have to choose. Sometimes, you can have your cake and eat it too. Turntable is concrete evidence that you can have best of both worlds.

 

Hanna Hoang

Applications Engineer

Application Data:

engineering 
better_together_using_two_frameworks_to_build_marketing_technology 
path /srv/www/sites/turn-dev.com/dev/repo/build/app 
main_controller app\controllers\Primary 

Request Data:

$_GET
No Data
$_POST
No Data
$_COOKIE
No Data
$_FILES
No Data
$_SERVER
REDIRECT_STATUS 200 
HTTP_HOST turn.stage.elusive-concepts.com 
HTTP_ACCEPT_ENCODING x-gzip, gzip, deflate 
HTTP_USER_AGENT CCBot/2.0 (http://commoncrawl.org/faq/) 
HTTP_ACCEPT text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 
PATH REMOVED 
SERVER_SIGNATURE Apache/2.4.10 (Linux/SUSE) Server at turn.stage.elusive-concepts.com Port 80 
SERVER_SOFTWARE Apache/2.4.10 (Linux/SUSE) 
SERVER_NAME turn.stage.elusive-concepts.com 
SERVER_ADDR 192.168.1.201 
SERVER_PORT 80 
REMOTE_ADDR 54.225.54.120 
DOCUMENT_ROOT /srv/www/sites/turn-dev.com/prod/webroot 
REQUEST_SCHEME http 
CONTEXT_PREFIX  
CONTEXT_DOCUMENT_ROOT /srv/www/sites/turn-dev.com/prod/webroot 
SERVER_ADMIN roger.soucy@elusive-concepts.com 
SCRIPT_FILENAME /srv/www/sites/turn-dev.com/prod/webroot/index.php 
REMOTE_PORT 50292 
REDIRECT_URL /engineering/better-together-using-two-frameworks-to-build-marketing-technology 
GATEWAY_INTERFACE CGI/1.1 
SERVER_PROTOCOL HTTP/1.0 
REQUEST_METHOD GET 
QUERY_STRING  
REQUEST_URI /engineering/better-together-using-two-frameworks-to-build-marketing-technology 
SCRIPT_NAME /index.php 
PATH_INFO /engineering/better-together-using-two-frameworks-to-build-marketing-technology 
PATH_TRANSLATED redirect:/index.php/engineering/better-together-using-two-frameworks-to-build-marketing-technology/better-together-using-two-frameworks-to-build-marketing-technology 
PHP_SELF /index.php/engineering/better-together-using-two-frameworks-to-build-marketing-technology 
REQUEST_TIME_FLOAT 1505977169.575 
REQUEST_TIME 1505977169 

Logs:

Time Data
2017-09-21 06:59:29
Loading Framework...
2017-09-21 06:59:29
Hanna Hoang
2017-09-21 06:59:29

Events:

Event Data Listeners
APPLICATION >> RUN null 0
APPLICATION >> LOADED null 0
APPLICATION >> HANDOFF null 0
TEMPLATE >> HTML_START "" 0
TEMPLATE >> BEFORE_HTML_END null 1

Errors:

Notice (8) Undefined variable: clean_author /srv/www/sites/turn-dev.com/dev/repo/build/app/controllers/class.engineering.php L: 305
Notice (8) Undefined index: f /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Trying to get property of non-object /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Undefined index: f /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Trying to get property of non-object /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Undefined index: f /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Trying to get property of non-object /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Undefined index: f /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Trying to get property of non-object /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Undefined index: f /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Trying to get property of non-object /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 72
Notice (8) Undefined index: image /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 137
Notice (8) Undefined index: facebooklink /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 161
Notice (8) Undefined index: twitterlink /srv/www/sites/turn-dev.com/dev/repo/build/tmp/smarty/templates_c/f7a7186590639363f640fdf7c56578099adb66c0.file.post.tpl.php L: 165

Benchmarks:

Benchmark Tag Time Comment
execution_time TIMER_START 0.000ms Starting bootstrap...
execution_time TIMER_STOP 114.287ms Debug console render output...