Using Google Optimize with Angular

Recently, we’ve started doing A/B testing on the StackFoundation website, to better understand our visitors in, and to test out changes in design and messaging. It allows us to experiment using different messaging strategies (even ones we would normally dismiss outright), and get real data on the effectiveness of those strategies, as opposed to relying on our biased views (our biases are a good topic for an entirely different discussion).

For that we are using Google Optimize – an A/B testing tool that ties into Google Analytics, and is both free and easy to setup.

How Optimize works

Google Optimize works by injecting/replacing content on the page for a certain percentage of users. The exact changes are usually setup through a Chrome plugin, and triggered on page load. In the case of single-page style websites like ours, changes are triggered by an event.

Using Google Optimize with Angular was, as it turns out, a very simple process. Angular has a pretty robust system of lifecycle hooks that we could use, and using the right one is all we need.

Setting a catch-all hook to trigger experiment changes

For nearly all use-cases, all we need is a catch-all. The worry we had here was that it might mean a lot of traffic going back and forward between the user’s machine and Google if a lot of changes occur. As it turns out, reactivating Optimize doesn’t mean new requests get made, so this is quite simple.

On the root component of your app, simply:

@Component({
    selector: 'my-app',
    template: 
    `<header></header>
    <div class="content" >
        <router-outlet></router-outlet>
    </div>
    <footer></footer>`
})
export class Root implements AfterViewChecked {
    ngAfterViewChecked () {
        if (window['dataLayer']) {
            window['dataLayer'].push({'event': 'optimize.activate'});
        }
    }
}

This will ensure any change to the html is followed by a check and/or replacement pass by the Optimize snippet, and as it happens on AfterView, no risk of it being overwritten, or colliding with Angular processes.

This has the added benefit of not having any flicker due to content change, except for when the page first loads.

Avoiding content flicker on initial page load

Google provides a page hiding snippet precisely because of this flicker. Ours being a single page app, we prefer to avoid any page content/style changes that fall beyond our control. In this particular case, it was a mistake – the snippet does its work very well, and unless a very specific and fine-grained control is necessary, it is definitely the way to go.

That being said, we explored how to avoid this flicker using only Angular, and here is the result of that. I cannot stress enough – this ended up not being used, as it was deemed better to use Google’s snippet.

Analysing the snippet provided by Google, we see that an object containing data on the specific Optimize account and a callback should be added under the hide property of the dataLayer object Google uses. We also noted that if dataLayer happens to have already been created, the callback won’t run – but if that happens, there is no risk of flicker. Using APP_INITIALIZER:

{
    provide: APP_INITIALIZER,
    useFactory: (...) => {
        return () => {
            return new Promise<boolean>(resolve => {
                if (!window['dataLayer']){
                    let _valObj: any = { 'GTM-XXXXXXX': true };
                    _valObj.start = Date.now();
                    _valObj.end = () => resolve(true);
                    window['dataLayer'] = [];
                    window['dataLayer'].hide = _valObj;
                    setTimeout(function () {
                        resolve(true);
                        _valObj.end = null
                    }, 4000);
                    _valObj.timeout = 4000;
                }
                else {
                    resolve(true);
                }
            });
        };
    },
    [...]
}

 

Leave a Reply

Your email address will not be published. Required fields are marked *