Angular Optimizely

Context

I recently had a need to integrate Optimizely into an AngularJS app. If you've researched integrating Angular and Optimizely before, you might have thought that this isn't possible at all given how Optimizely generally requires content to be on the page before it runs and how Angular apps won't have any content on the page at all when document.onready triggers. Even if you figured it must be possible, there are so many technical constraints you might not think it's worth it. It turns out however that:

Angular and Optimizely are compatible after all!

Considerations

Angular

AngularJS is super popular so if you're integrating another super popular service with an MVC framework, it's probably going to be this one. It's decently designed, well-structured and has TONS of power and a great community.

Angular seems to be a problem for Optimizely because the Optimizely tests get automatically triggered (if you use the standard implementation) as soon as the document is ready. Unfortunately, Angular has no way of delaying the document ready event. You can manually trigger Optimizely after the Angular $viewContentLoaded event but that actually only triggers when things are loaded. Not when they're in the DOM.

Optimizely

If you haven't at least played with Optimizely's experiment designer you haven't lived. I am convinced that in the near future, all web design/development will be done on a platform built on their editor. It's amazing. No other A/B service has the powerful and easy-to-use editor that they have.

Optimizely tests are only meant to be run on certain pages. In Angular this corresponds to views but if you never actually change pages, how will Optimizely know to run? You can, of course, manually trigger experiments but then you'll have to add a hook to trigger a specific experiment at the end of every view insertion and that's just kind of a pain. Then there's the problem that Optimizely almost invariably causes a FOUC because content will start appearing on your page well before the document is fully ready. Waiting for the document to be ready means that users will either see content they're not supposed to or that they'll see things shift around in a disorienting manner after they've already started browsing.

The Solution

I wrote a plugin!

If you want to skip ahead and download it, my Angular Optimizely plugin is available for free under an MIT license on GitHub.

In order to actually get Optimizely to run after the view is in place, we manually trigger experiments after the view has loaded. Getting the experiment to trigger after the view has really loaded and not just when the premature $viewContentLoaded event is triggered requires an interesting hack which is to wrap the Optimizely trigger in a $timeout block. Putting the event in a $timeout instead of vanilla JS means that it's going to be added to Angular's event queue. And what, do you ask, is on the queue right before that $timeout? All of the rendering of content into the DOM that we require.

Manually triggering each experiment individually is a huge pain and would lead to a TON of repeated code. Fortunately there's a poorly known feature of Optimizely where if you call the activate method without an experiment ID, it will look at the current path and available experiments and trigger the ones that are able to be run on that "page". This means that we can use a generic trigger for all the experiments and not have to add an Optimizely call to every single controller and directive. Nice, right?

Our last concern is avoiding that nasty FOUC. Fortunately, for regular views even with remote templates being called in via AJAX, the $viewContentLoaded and $timeout will happen so quickly that nobody would notice them even if they took a really long time to run. Something that is a concern though is loading in data/JSON via AJAX as opposed to just a view template. For this problem, I propose a 2-prong solution. First, you're going to need to trigger Optimizely after the data has come in and for that I suggest creating a postLoad() hook that you can stick in after any and all async data requests. That way you can hook in not just Optimizely but any other services you'd like to trigger after a data request comes back. Secondly, you might still run into FOUCs if the experiment touches other things in the view than the data. For this, I would recommend taking advantage of native Angular functionality and hiding the other content until the data is in scope. For example: If you're running tests on your blog and load posts asynchronously, adding this to the page/content wrapper would keep things nice and smooth: <div class='content-wrapper' ng-show='blogPosts'>. That way the whole .content-wrapper div would only show up once the posts are in scope.

Conclusion

I hope that you use the plugin and can benefit from the process I outlined here. Please let me know if you have questions, find a bug or find the plugin useful. A star on Gihub takes almost no time and makes my day ;).