cssAudio - Activefile-genericCSS - ActiveGeneric - ActiveHTML - ActiveImage - ActiveJS - ActiveSVG - ActiveText - Activefile-genericVideo - ActiveLovehtmlicon-new-collectionicon-personicon-teamlog-outoctocatpop-outspinnerstartv

Pen Settings

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

Looking for quick-add? Try the external resource search, it's quicker and gives you access to the most recent version of thousands of libraries. ☝️

You're using npm packages, so we've auto-selected Babel for you here, which we require to process imports and make it all work. If you need to use a different JavaScript preprocessor, remove the packages in the npm tab.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Looking for quick-add? Try the external resource search, it's quicker and gives you access to the most recent version of thousands of libraries. ☝️

Use npm Packages

We can make npm packages available for you to use in your JavaScript. We use webpack to prepare them and make them available to import. We'll also process your JavaScript with Babel.

⚠️ This feature can only be used by logged in users.

Code Indentation

     

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

            
              <!-- this is the usual Google Analytics Embed code with the trackPageView call removed. Angulartics module should make that call instead -->

<script>
  (function(i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r;
    i[r] = i[r] || function() {
      (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o),
      m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
  })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');

  ga('create', 'UA-8842961-2', 'auto');
</script>

<!-- The Google Experiments tracking code is not embedded here as Google recommends. The Google Experiments module for Angular takes care of loading that later -->

<h1>Let's try Google Experiments A/B Testing with AngularJS</h1>

<!-- Here's a controller so I have place to hook up a button -->
<div ng-app="LetsTryGoogleExperiments" ng-controller="ContentController as vm">

  <!-- set up 2 possible content variations. Google Analytics will choose a variation to show, and the googleExperiments module's variation directive will hide the other variations -->

  <!-- use ng-cloak class to hide content while Google Analytics is loading and initializing -->
  <div variation="0" class="ng-cloak">
    <p style="color:blue">I am the <b>original</b> content.</p>
  </div>

  <div variation="1" class="ng-cloak">
    <p style="color:green">I am <b>variation 1.</b></p>
  </div>

  <button ng-click="vm.onButtonClick()">Clicking me fires an event which is counted as a conversion in Google Analytics</button>

</div>

<hr>

<h2>What does this do?</h2>

<p>A bunch of Angular modules are working together to show and hide different content on this page, and track which variation of the content results in the most button clicks. This technique is useful for optimizing websites to achieve their goals (sales, sign-ups, lower bounce rates etc).</p>

<p>I'm mostly interested in seeing <a href="https://github.com/omouse/angular-google-experiments">angular-google-experiments</a> in action with this page.</p>

<h2>How do I see it work?</h2>
<p>Assuming you have access to my GA account (you probably don't):</p>
<ol>
  <li>open the <a href="https://s.codepen.io/adamsullovey/debug/QEAaBw">debug version of this Pen</a> in a new incognito/private browsing window</li>
  <li>take note of which variation of the content that appears above the button</li>
  <li>click the button if you want</li>
  <li>close the browser window</li>
</ol>

<p>Repeat this process ~5 times and watch as Google Analytics chooses a variation to show you. Behind the scenes, it is keeping track of which variation results in the most button clicks.</p>

<p>If you don't use an incognito version of the window, you should see the same content on every page load unless you clear your cookies. Google Experiments keeps the experience consistent for each visitor instead of swapping things on each of their visits.</p>

<p>Meanwhile, inside Google Analytics reports you can watch the # of conversions in realtime under the Realtime > Conversions section. After enough data has been gathered (probably after 100s of clicks on the button), some experiment data should be visible
  under the Behaviour > Experiments section.</p>

<h2>Limitations?</h2>

<ul>
  <li>Can I run multiple experiments at once in 1 app?</li>
  <li>Does this work well with UI router and other common modules?</li>
  <li>Can I use it for just swapping css classes instead of entire HTML elements?</li>
</ul>
            
          
!
            
              button {
  display:block;
  font-size:1rem;
  padding:0.5rem;
  border-radius:10px;
}

body {
  font-family:sans-serif;
  line-height:1.4;
  max-width:600px;
  margin:3rem auto;
}

h1, h2 {
  font-weight:normal;
}
            
          
!
            
              angular.module('LetsTryGoogleExperiments', [
  'angulartics', // loaded in the pen's settings
  'angulartics.google.analytics', // loaded in the pen's settings
  'googleExperiments' // hardedcoded in this pen below
])
/**
 * this controller is specific to this project. It just makes sure button clicks are sent to Google Analytics as events
 */
.controller('ContentController', function($analytics) {

  this.onButtonClick = () => $analytics.eventTrack('convert-click');

})

.config(function(googleExperimentsProvider) {
  googleExperimentsProvider.configure({
    // this id is provided by Google
    experimentId: '03dxWc-3QiugZYUEC5Zfhg'
  });
})

;

/**
 * This is the Google Experiments module from:
 * https://github.com/omouse/angular-google-experiments/blob/master/googleExperiments.js
 * I couldn't find it on a major CDN, so it is embedded here
 */

angular.module('googleExperiments', ['angularLoad']);

angular.module('googleExperiments').directive(
  'variation', ['googleExperiments', function(googleExperiments) {
    return function(scope, element, attr) {
      element.addClass('ng-cloak');
      scope.$watch(attr.variation, function googleExperimentsVariationWatchAction(value) {
        googleExperiments.getVariation().then(function(variation) {
          if (variation == value) {
            element.removeClass('ng-cloak');
            element.removeClass('ng-hide');
          } else {
            element.addClass('ng-hide');
          }
        });
      });
    };
  }]
);;
angular.module('googleExperiments').provider(
  'googleExperiments', [function googleExperimentsProvider() {
    var variation;
    this.configure = function(conf) {
      this.config = conf;
    };

    this.$get = ['$q', '$timeout', 'angularLoad', function($q, $timeout, angularLoad) {
      var variationDeferred = $q.defer();

      angularLoad.loadScript('//www.google-analytics.com/cx/api.js?experiment=' + this.config.experimentId).then(function() {
        variationDeferred.resolve(cxApi.chooseVariation());
      }).catch(function() {
        //error loading script
      });

      return {
        getVariation: function() {
          return variationDeferred.promise;
        }
      };
    }];
  }]
);

/**
* I couldn't find angular-load.js on a major CDN, so it is embedded here too
* https://github.com/urish/angular-load/blob/master/angular-load.js
*/

/* angular-load.js / v0.4.1 / (c) 2014, 2015, 2016 Uri Shaked / MIT Licence */

(function() {
  'use strict';

  angular.module('angularLoad', [])
    .service('angularLoad', ['$document', '$q', '$timeout', function($document, $q, $timeout) {
      var document = $document[0];

      function loader(createElement) {
        var promises = {};

        return function(url) {
          if (typeof promises[url] === 'undefined') {
            var deferred = $q.defer();
            var element = createElement(url);

            element.onload = element.onreadystatechange = function(e) {
              if (element.readyState && element.readyState !== 'complete' && element.readyState !== 'loaded') {
                return;
              }

              $timeout(function() {
                deferred.resolve(e);
              });
            };
            element.onerror = function(e) {
              $timeout(function() {
                deferred.reject(e);
              });
            };

            promises[url] = deferred.promise;
          }

          return promises[url];
        };
      }

      /**
       * Dynamically loads the given script
       * @param src The url of the script to load dynamically
       * @returns {*} Promise that will be resolved once the script has been loaded.
       */
      this.loadScript = loader(function(src) {
        var script = document.createElement('script');

        script.src = src;

        document.body.appendChild(script);
        return script;
      });

      /**
       * Dynamically loads the given CSS file
       * @param href The url of the CSS to load dynamically
       * @returns {*} Promise that will be resolved once the CSS file has been loaded.
       */
      this.loadCSS = loader(function(href) {
        var style = document.createElement('link');

        style.rel = 'stylesheet';
        style.type = 'text/css';
        style.href = href;

        document.head.appendChild(style);
        return style;
      });
    }]);
})();
            
          
!
999px
🕑 One or more of the npm packages you are using needs to be built. You're the first person to ever need it! We're building it right now and your preview will start updating again when it's ready.
Loading ..................

Console