Optimizely and Angular

A couple of days ago Qwilr announced the release of our Beta, and as part of this launch we decided to run a little experiment on our unsuspecting audience. Using Optimizely, we did an A/B test of text on the welcome page in order to determine whether changes to the way we described the product would affect people clicking through to sign up (SPOILER ALERT: it did, but that’s not the focus here).

We had a small issue setting it up, which took a bit of digging through the Optimizely docs to work out, so I thought I’d write about it here for future reference. Optimizely worked great ‘out-of-the-box’. Even our business guru, Steve, was able to set up an A/B test, despite having nearly no technical knowledge (sorry Steve).

The only drawback to using Optimizely? Angular and Optimizely competing with DOM manipulation. Optimizely works by doing some DOM manipulation of the chosen website; a small snippet of JavaScript executes and changes some of the values being displayed. Unfortunately there is a race condition when using it with Angular, as Angular is also manipulating the DOM. Qwilr’s index.html body looks like this on initial page load:


    <body ng-controller="QwilrWebsiteController">
        <ui-view class='viewport'></ui-view>
        <!-- Compiled JavaScript -->
        <script type="text/javascript" src="/Website/Assets/Qwilr-Website-2.1.0.js"></script>
    </body>

So if Optimizely attempts to manipulate the DOM now (as is the default behaviour) it would be forgiven for missing those one or two elements that Angular hasn’t yet injected. The solution that Optimizely suggests is to switch the snippet execution to manual triggering (if you created an Optimizely ‘campaign’ the included snippet code is set to automatically trigger on page load) and add a delaying function that waits until the portion of the page that you want to manipulate has loaded:


waitForDelayedContent = (selector, experiment, timeout, keepAlive) ->
    intervalTime = 50
    timeout = timeout || 3000
    keepAlive = keepAlive || false
    maxAttempts = timeout / intervalTime
    attempts = 0
    elementsCount = 0
    interval = setInterval () ->
    if ($(selector).length > elementsCount)
        if (!keepAlive)
            clearInterval(interval)
        experiment()
        elementsCount = $(selector).length
    else if (attempts > maxAttempts)
        clearInterval(interval)
    attempts++
  , intervalTime
waitForDelayedContent(".hero .button", () ->
    window.optimizely.push(["activate", 0123456789])
)

The above just defines a function that checks for the provided selector until a timeout. When it finds the selector in the DOM it executes the provided function, which in this case tells the Optimizely snippet to activate experiment ‘0123456789’. The selector is one of the elements that the Optimizely snippet would be manipulating.

This solution is a little hacky, as its correct behaviour is dependent on which selector is chosen. If you choose a selector that is injected well before other elements, then the delay will trigger the experiment’s start too soon, so some care is necessary. However, once we added in this delay the A/B test was working as expected… and it was on to the Beta announcement!

ben-lambert-smith
Ben Lambert-Smith is a Software Engineer at Qwilr

Ben holds a Computer Science degree from the University of NSW, and worked at Microsoft in Seattle, before joining Qwilr.