Integrating AngularJS And Google Tag Manager

May 4, 2016
Integrating AngularJS And Google Tag Manager

If you’ve added Google Tag Manager and Google Analytics to an Angular app, you were in for a surprise. After deploying your code, you may have popped open the Real Time Reports and saw… nothing.

Well, not nothing, but not a whole lot of anything. Where are the page paths? Hell, where are the pageviews? You dove into the Google Analytics docs and came back disappointed. Finally, you hit the search results – someone, somewhere had to know what to do.

Welcome, friend! You’ve come to the right place. Let’s get you straightened out.

But First, The Easy Button


Not interested in what’s going on under the hood? The module you’re looking for is Angulartics (Full disclosure: I’m a contributor on the GA and GTM libraries). It supports:

  • ngRoute and UIRouter
  • Google Tag Manager & Hard-coded Google Analytics
  • Declarative event tracking
  • Advanced hit types like User Timings
  • Exception tracking, scroll tracking, and more advanced features
  • 20+ analytics tools

And there’s a GTM Container file for one-click configuration written by yours truly in the repository.

Get the code here!

What Is Google Tag Manager?

Google Tag Manager (also referred to as GTM) is a tool for delivering Google Analytics tracking code and other tracking snippets on a page. It helps de-clutter your code by moving tracking snippets into a single location, instead of littered throughout the page. It features a WYSIWYG-style interface for creating, testing, and publishing additional tracking snippets on the fly, called Tags. Tags are assigned conditions on when they should be executed, called Triggers. In a nutshell, here’s how it works:

  1. A user interaction (clicking, submitting a form) or programmatic notification is observed by GTM (called an event)
  2. Each Trigger is evaluated against the event to see if the firing conditions set in the interface are met. If they are, the Trigger ‘fires’
  3. All Tags assigned to any Triggers that fire are executed

Most commonly, Google Tag Manager is used to deploy Google Analytics tags, from basic pageview tracking all the way up to complex custom Google Analytics Events. For a more in-depth explanation, my colleague Kaelin wrote a great introduction to Google Tag Manager and the relationship between GTM and Google Analytics.

Our Cookbook

Below, we’ve shared some components that we’ve used in the past. These components are designed to minimize the amount of additional code required to integrate Google Tag Manager and an Angular app. Because GTM sits on top of Google Analytics, the data that you give to it is inert by default; whether anything is fired is controlled in the interface. Because of this, it’s a good idea to be inclusive with the data you share with the dataLayer.

Pageview Tracking

The default Trigger for firing pageviews is called All Pages. The event that this Trigger corresponds to is gtm.js, which fires only when the GTM snippet is first loaded by the browser. This is why your pageviews haven’t been showing up. You need to add an internal listener in your app that notifies GTM when a state change occurs. If you’re using ngRoute, it would look like this:

(function(angular) {

  angular
    .module('app', ['ngRoute'])
    .run($run);

  // Safely instantiate dataLayer
  $run.$inject = ['$rootScope', '$location', '$window'];

  function $run($rootScope, $location, $window) {

    var dataLayer = $window.dataLayer = $window.dataLayer || [];

    $rootScope.$on('$routeChangeSuccess', function() {

      dataLayer.push({
        event: 'ngRouteChange',
        attributes: {
          route: $location.path()
        }
      });

    });

  }

})(angular);

Once you’ve wired this into your app, Google Tag Manager will be notified any time a route change occurs. In order to translate this into a pageview in Google Analytics, you’ll need to create a Trigger for your ngRouteChange event, like so:

GTM Trigger for Angular Route Change

Then create a Variable to extract your page path from the dataLayer, like so:

GTM Variable for Angular Route

And you’ll need to create a Google Analytics pageview tag, like so:

GTM Tag for Angular Pageview

Don’t forget the Fields to Set configurations!

  • cookieDomain is set to auto
  • page is set to {{DLV – Angular Route}}

If you’re unwilling to wire into your router, you can create a Trigger using the History Change listener built into GTM, watching for pushState events, but we don’t recommend this approach.

Model Change Tracking

Often, you may want to track changes to model values in Google Analytics. A simple way to do this is with a custom Directive that notifies Google Analytics or Google Tag Manager when a model’s value changes. A simple solution for this challenge is to use a custom Directive that binds to the change event.

(function(angular) {

  angular
    .module('app', [])
    .directive('notifyGtm', notifyGtm);

  notifyGtm.$inject = ['$window'];

  function notifyGtm($window) {

    var dataLayer = $window.dataLayer = $window.dataLayer || [];

    function link(scope, element, attributes, ngModel) {

      element.bind('change', function() {

        var el = element && element[0] ? element[0] : '';

        if (el) {

          dataLayer.push({
            event: 'ngNotifyGtm',
            attributes: {
              element: el,
              modelValue: ngModel.$modelValue,
              viewValue: ngModel.$viewValue
            }
          });

        }

      });

    }

    return {
      require: 'ngModel',
      restrict: 'A',
      link: link,
      scope: {
        ngModel: '='
      }
    };

  }

})(angular);

You can then add notify-gtm on any element with an ng-model that you’d like to observe with Google Tag Manager. In Google Tag Manager, you can use a Data Layer Variable to extract the value of the model or view, or use the element object reference to pull out additional data.

Notifying GTM After A Template Renders

Often, we depend on information in the DOM when capturing data to send to Google Analytics. Normally, we can use the gtm.dom or gtm.load as safe-guards for firing our code, but with an Angular app, it can be hard to ensure the data is ready to go. Unfortunately, the best solution I’ve seen so far is equally ugly; creating a Directive that triggers a function after rendering (hat tip to Guilherme Ferreira).


 
 

This provides a balance of flexibility and discretion, but it means you’ll have to remember to add the directive at the bottom of each of your templates. If you’ve got more clever solution, please share it in the comments below.

Tracking Other Interactions

If you’d like to use Google Tag Manager and Google Analytics more extensively in your application, take advantage of Google Tag Manager’s reusable Variables to streamline the process. First, you’ll want to build a reusable service for interacting with GTM.

(function(angular) {

  angular
    .module('app', [])
    .service('GTMService', GTMService);

  GTMService.$inject = ['$window'];

  function GTMService($window) {

    var dataLayer = $window.dataLayer = $window.dataLayer || [];

    return function GTMService(obj) {

      dataLayer.push(obj);

    };

  }

})(angular);

This can be further evolved to streamline things like event tracking.

(function(angular) {

  angular
    .module('app', [])
    .service('GTMService', GTMService);

  GTMService.$inject = ['$window'];

  function GTMService($window) {

    var dataLayer = $window.dataLayer = $window.dataLayer || [];

    function api(obj) {

      dataLayer.push(obj);

    }

    api.trackEvent = function(obj) {

      var attr = obj.attributes;
      var abort;

      angular.forEach(['category', 'action'], function(el) {
 
        var err;

        if (typeof attr[el] === 'undefined') {
          err = new Error('GTMService.trackEvent: Missing required property ' + el + '. Aborting hit.');
          throw err;
        }

      });

      push({
        'event': 'ngTrackEvent',
        'attributes': {
          'category': attr.category,
          'action': attr.action,
          'label': attr.label,
          'value': attr.value,
          'nonInteraction': attr.nonInteraction
        }
      });

    }

    return push;

  }

})(angular);

 

With the above example, you could create a single Tag and Trigger for your Event Tracking, and simply invoke GTMService.eventTrack whenever you'd like to fire an event.

The Angulartics source code is a great place to learn more about different ways to integrate the two for maximum ease of use.

Other Front-end Frameworks

Many of these same concepts apply to other frameworks, e.g. Knockout, Backbone, and so on. If you'd be interested in knowing how we would approach integrating Google Tag Manager with another framework, let us know in the comments and we'll see about writing a post covering that particular library.

To Recap

These components should help get you started. Remember, the data pushed to the dataLayer is inert by default, so lean towards 'over-sharing'. You can automate pageview tracking, model change observation, and template rendering with our example components, and you can use a simple service to interact with the dataLayer in your controllers and other components. Finally, you can further streamline things like event tracking by creating your own services, and I'd recommend reviewing the Angulartics module if you'd like to learn more.

Have you integrated Google Tag Manager and Angular before? What was your experience? Share it with us in the comments.