Using Google Tag Manager with AngularJS

One would expect that being Angular a Google product and Google Tag Manager another of their products combining them would be quite simple and straightforward, but I personally had my doubts and not much documentation on how are we supposed to make them work. Should I just paste the code as usual or will that get into conflict with Angular? Is it going to register “page views” whenever I change the routing or not as it’s not a standard page view? I checked some things and here are some of my results:

Adding Google Tag Manager snippet into the page

First of all, I didn’t find any problem with just copy-pasting the code before the /body closing tag, but I wanted to angularize it and have everything in one piece of code (so that all stuff related to GTM is on the same file). So instead of doing that I went for this angular way to insert the code:

angular.element(document).ready(function () {
(function (w, d, s, l, i) {
w[l] = w[l] || []; w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})($window, document, 'script', 'tm', 'YOUR-GTM-CODE');
//note: I've changed original code to use $window instead of window
});

I’m quite sure that the object window would work too but it made me feel more comfortable to make sure angular will handle it properly.

Adding user related data to GTM

I had a bit more of difficulty here, and that’s the main reason I came into all this trouble. I needed to add some stuff to GTM but couldn’t do that until I got the data from the server in an asynchronous request, once I had it I would be able to populate that data to GTM. Also, most websites tell you to pre-generate the dataLayer object with such data and GTM will consume it by default. As I had to wait for the data such approach didn’t suit me, so I did the request, set an emit and listened for it before adding it to the Google Tag Manager object:

// set user details
$rootScope.$on("analyticalDataRetrieved", function (userDetails) {
if ($window.tm) {
$window.tm.push(
{
'a': userDetails.Age,
'm': userDetails.MemberType
});
}
});

That’s pushing the user details into GTM object (which in my case is tm and not dl or dataLayer btw) asynchronously. Seems to work.

Tracking events

Of course the main goal of Google Tag Manager is to track website events so that the Product Manager is happy. To do this you need to push into the GTM object (tm in my case) the event. If you do so, GTM won’t do nothing unless such event has been configured on server side, be warned of that (it took me a while to find out). This is, you need to access Google Tag Manager admin area, add this event (in my example, “Paywall”), and then push it to the object on client-side:

// Note: event is used to trigger the GoogleTagManager tracker, but its value is not sent to the server.
// rest of values are sent to server as category, action, label (there's also value if we need it)
// ec -> category, ea -> action, el -> label
$rootScope.$on('upgradeMembershipClicked', function (event, data) {
if ($window.tm) {
$window.tm.push({ event: 'Paywall', ec: 'Paywall', ea: 'Click', el: data.gtmLabel });
}
});

Because the property “event” is only the keyword to check if that’s a tracking event or not by GTM, it’s not sent to the server as a value to track, just the “signal” to do it. Rest of properties (event category, event action, event label and event value) are sent to the server if you include them.

By the way, I decided to emit the events I want to register on GTM so that the GTM service can just listen to them and handle them as a way of having all GTM related logic together. When those events are emitted they don’t really bring me that data.gtmLabel but it simplified this example.

Registering page views

I’m still investigating this one, but shouldn’t be that difficult. It’s probably a matter of listening to the “$routeChangeSuccess” and pushing the “pageView” event into the object:

$rootScope.$on('$routeChangeSuccess', function (event) {
if ($window.tm) {
// this is an example I found around there using Google Analytics (ga), need to find out how to do it with GTM:
//$window.ga('send', 'pageview', { page: $location.path() });
}
});

Conclusion

This is the final service on a whole:

app.service('analyticsHandler', function ($rootScope, $window) {
angular.element(document).ready(function () {
(function (w, d, s, l, i) {
w[l] = w[l] || []; w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.async = true;
j.src = '//www.googletagmanager.com/gtm.js?id=' + i + dl;
f.parentNode.insertBefore(j, f);
})($window, document, 'script', 'tm', 'YOUR-GTM-CODE');
//note: I've changed original code to use $window instead of window
});

// set user details
$rootScope.$on("analyticalDataRetrieved", function (userDetails) {
if ($window.tm) {
$window.tm.push(
{
'a': userDetails.Age,
'm': userDetails.MemberType
});
}
});

$rootScope.$on('$routeChangeSuccess', function (event) {
if ($window.tm) {
// this is an example I found around there using Google Analytics (ga), need to find out how to do it with GTM:
//$window.ga('send', 'pageview', { page: $location.path() });
}
});

// Note: event is used to trigger the GoogleTagManager tracker, but its value is not sent to the server.
// rest of values are sent to server as category, action, label (there's also value if we need it)
// ec -> category, ea -> action, el -> label
$rootScope.$on('upgradeMembershipClicked', function (event, data) {
if ($window.tm) {
$window.tm.push({ event: 'Paywall', ec: 'Paywall', ea: 'Click', el: data.gtmLabel });
}
});
}