Apps built with AngularJS provide an interesting challenge for real user monitoring (RUM) solutions. Here’s how we overcame those challenges at SOASTA.
AngularJS is a modern web development framework that is fast becoming one of today’s most popular tools for web developers. Sites built with frameworks like AngularJS live in the category of “single-page applications” (SPAs). One of the core differentiators of SPAs over “traditionally” built websites is that they run on a single page, dynamically bringing in content as necessary. (Other notable SPA frameworks include Ember.js, Backbone.js and React.)
Apps built with AngularJS present three major challenges.
After the AngularJS framework has loaded and displayed the desired page, subsequent interactions that would have traditionally navigated the browser to a new URL no longer do so. The framework is responsible for fetching any new content on the fly and dynamically showing it to the user. RUM solutions that measure page loads do not have insight into these in-page navigations (sometimes called soft navigations).
We’re seeing a growing need to be able to monitor today’s latest breed of websites built with SPA frameworks such as AngularJS. To do so, we’ve made improvements to Boomerang, which is responsible for gathering performance metrics for mPulse.
There are three major challenges when trying to accurately gather RUM performance metrics for AngularJS:
Challenge #1: The
onload event no longer matters.
The first issue is that the
onload event no longer matters for SPAs.
After the browser has finished downloading all of the statically included resources, it will fire the body’s
onloadevent, which is the traditional signal that the page load has completed. This is also the event that most performance tools hook into to measure page load performance.
The problem with the
onload event does not wait for any of these dynamically injected resources. Thus measuring when the
onload event fires is only measuring up to the point that the web app’s core HTML and angular.js finished downloading — much earlier than when the page could be interacted with by the user.
Depending on how much content the AngularJS app is displaying to the user, that point of interaction could be up to several seconds after the
Here’s a visual representation of this challenge:
The above waterfall is from WebPagetest of the AngularJS Sample App. The browser fires the
onload event at 1.225 seconds (in the middle of the image, near the purple vertical bar), while the majority of the visual resources (.jpgs) don’t start downloading until after 1.5 seconds or so. The WebPagetest filmstrip shows that nothing is displayed to the user until about 1.7 seconds and the page doesn’t appear visually complete until about 1.9 seconds:
onload event just isn’t useful for single-page applications. In the above example,
onload fires 700 milliseconds before the user sees any content.
Challenge #2: Soft navigations are not real navigations.
The second issue is that SPAs do not trigger a new browser navigation.
Once the first page has loaded, a visitor to an AngularJS app will be navigating around the content. Each of the links clicked tell AngularJS to fetch content and dynamically swap it into the current view (soft navigations). As a result, the browser is no longer doing a traditional “navigation”, where it tears down the old page, fetches the new HTML, and builds the new page up from scratch.
The challenge is that soft navigations leave traditional performance analytics tools in the dark as they assume a page load happens only once per browser navigation.
Challenge #3: The browser won’t tell you when all resources have been downloaded.
The third issue is a side effect of the first two issues: the browser no longer fires an
onload event after the first navigation, so subsequent soft navigations don’t fire the
onload event again to tell the page when all of the resources have been fetched.
onload event once they are complete.
In a soft navigation scenario, the browser does not fire the
onload event again. AngularJS itself fires a couple events when the page navigates (such as
$viewContentLoaded), but these events are only when the new HTML is swapped into the page, not when the downloads are complete.
Thus AngularJS’s events alone are not a good corollary to the traditional
Two major improvements were made to address the three challenges above:
- First, we now listen for AngularJS routing events, such as
$routeChangeStart. Once we see that the visitor is about to view new content, we start listening in preparation for gathering performance metrics for the soft navigation.
- Second, and most importantly, when the page is first loading, and when a soft navigation starts, we begin monitoring the HTML document for any new downloadable resources that get inserted. We do this via a MutationObserver.
For example, take the AngularJS Sample App. It sends an XHR to gather details about all of the products, which contain links to those product images. All of these
<IMG>s are inserted into the document by an AngularJS template, and the browser starts downloading them.
Remember that, after the initial page load, the browser will no longer be firing an
Here’s an annotated timeline that shows how the MutationObserver helps us monitor all of the resources initiated by AngularJS:
Once you have the correct timeframe for the soft navigation, you may also want to gather performance information for all of the relevant resources that were fetched (via ResourceTiming). One problem is that the ResourceTiming buffer is fixed by default to 150 entries. A soft navigation doesn’t clear the buffer, so you may want to increase it yourself, or intelligently clear it on every soft navigation. We’ve posted some instructions on how to do this in our documentation section.
We’ve built this solution into Boomerang, which is available for all SOASTA customers. The changes will be making their way to the open-source Boomerang shortly as well.
Instructions for using Boomerang with AngularJS in our documentation section.