How I've integrated Google Tag Manager in an Angular app

This is an alternative simple approach to integrate google tag manager in an Angular app and start tracking page views and events. There are libraries and modules out there you could use, but this method focuses on directly integrating the tag and referencing it in the angular app to track stuff.

Please refer to the detailed explaination if you are completely new to this. Below is a quick snippet for experienced and busy developers who have worked extensively with Google Tag Manager and Angular.

We will...

  • Integrate the Tag manager code in index.html file
  • Make sure the window's dataLayer object is avaliable before GTM does it's job
  • Create a seperate service to get hold of the window's datalayer object. (Optional if you are comfortable referencing this object in every component you need it)
  • Create a seperate service to manage all dataLayer calls. (Optional if you would rather just code your dataLayer calls in specific components)
  • For tracking pageViews: Get hold of the router events to fire the URL to the service.
  • For tracking events or custom dimensions : Use events calls in components whereever we need to track events.

Integrate the script in index.html

First thing first, when we sign up for Google Tag manager, we get hold of the script that needs to be placed on every page. We'll place this script in the head tag of our index.html file of our Angular app.

This loads the tag manager.

Example below :

  • We have placed the script tag between our head tags. Note: Your tracking code would be different and you need to replace what you recevie from GTM at 'GTM-INSERT_YOUR_CODE_FROM_GTM_HERE' in the code below.
<head>
 <meta charset="utf-8">
 <title>TestApp</title>
 <base href="/">
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" type="image/x-icon" href="favicon.ico">
 <!-- Google Tag Manager -->
 <script>(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 =
         'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
   })(window, document, 'script', 'dataLayer', 'GTM-INSERT_YOUR_CODE_FROM_GTM_HERE');</script>
 <!-- End Google Tag Manager -->

</head>


Make sure window.dataLayer object is available

GTM reads the dataLayer object in your window to track stuff. In other words, you push all the pageviews, events etc to the dataLayer object and GTM will read from this 'store'. When the page loads, chances are you might start pushing pageviews and or events to the dataLayer object, whilst or even before GTM has loaded. So just to make sure, the object exists, we have initialised it before hand.

So in this case, our tag would look as below. Note the empty initialisation of the window.dataLayer right after the opening script tag.

.....
 <link rel="icon" type="image/x-icon" href="favicon.ico">
 <!-- Google Tag Manager -->
 <script>
     window.dataLayer = window.dataLayer || [];
 
 (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 =
         'https://www.googletagmanager.com/gtm.js?id=' + i + dl; f.parentNode.insertBefore(j, f);
   })(window, document, 'script', 'dataLayer', 'GTM-INSERT_YOUR_CODE_FROM_GTM_HERE');</script>
 <!-- End Google Tag Manager -->
....
</head>

The object is assigned to itself it it exists else initialised as a blank array.

Create window reference service

This is optional if you would like to access the window object separately in every other component, but as a principle of keeping it DRY and organised, I have created a service which offers the window object whereever this service would be consumed.

Let's call this WindowReferenceService. So my window-reference.service.ts would look like as below :


import { Injectable } from '@angular/core';

function getWindow():any {
    return window;
}

@Injectable({
    providedIn: 'root'
})
export class WindowReferenceService {
    get nativeWindow() {
        return getWindow();
    }
}

Note: You could make the return type of nativeWindow of type ICustomWindow but since we will inject custom dataLayer object, I would refrain from it.

Create dataLayer service to manage all dataLayer code

Now that we have access to the window object in our Angular app, we can start interacting and pushing stuff onto it. I have created the dataLayer service so that all dataLayer stuff could exists at a single location in the service. If this is not needed for your architecture, please feel free to skip this implementation and reference the window service or object directly in your components.

Below is what we have done with the dataLayer service.


import { Injectable } from '@angular/core';
import { WindowReferenceService } from './window-reference.service';


@Injectable({
    providedIn: 'root'
})
export class DataLayerService {
   private window; 

   constructor (private _windowRef: WindowReferenceService)
   {
       this.window = _windowRef.nativeWindow; // intialise the window to what we get from our window service

   }

    private pingHome(obj)
    {
        if(obj)  this.window.dataLayer.push(obj);
    }
   
   
   //list of all our dataLayer methods

   logPageView(url)
   {
       const hit = {
           event: 'content-view',
           pageName: url
       };
       this.pingHome(hit);
   }

   logEvent(event,category,action,label)
   {
       const hit = {
           event:event,
           category:category,
           action:action,
           label: label
       }
        this.pingHome(hit);
   }

   logCustomDimensionTest(value)
   {
       const hit = {
           event:'custom-dimension',
           value:value
       }
       this.pingHome(hit);
   }

   // .. add more custom methods as needed by your app.
}

Log Pageviews from Angular app to GTM

Now that we have all the services ready- it is time to track the pageViews. We'll get hold of our router and subscribe to its events. If the event is of our concern we inject the URL to our datalayer service. That way, we track all the pageViews.

So in our app.component.ts, here's what we have changed.


import { Router, NavigationEnd } from '@angular/router'; // gets router and navend
import { DataLayerService } from './data-layer.service'; // gets hold of the data layer service we just created

...
....
export class AppComponent {
...
...
constructor(private _router:Router, private _dataLayerService: DataLayerService)
{

    this._router.events.subscribe(event=> { // subscribe to router events
        if (event instanceof NavigationEnd) //if our event is of our interest
        {
            this._dataLayerService.logPageView(event.url) //call our dataLayer service's page view method to ping home with the url value.
        }
    });

}


}

Events call in components

In order to log custom events to the service, we could begin with injecting the dataLayer service we created above and call it's logEvent method wherever required.

For example, a usage below.



import { DataLayerService } from './data-layer.service'; // gets hold of the data layer service we just created

...
....
export class MyTestComponent {
...
...
constructor( private _dataLayerService: DataLayerService){}

buttonOfInterestClicked()
{
   //call the service's logEvent method
   this._dataLayerService.logEvent("'ButtonClicked'","'Buttons'","'Clicked'","'InterestingButton'");

    // continue with logic for what needs to be done in this method.
}

}

So to summarise, here's what we did.

  • Injected the tags in the root html, making sure the window object is initialised in prior.
  • Create service or services that manages the hits and pushes them to the window object
  • Integrate the service in router for pageviews and any other components you may need for events or custom dimension hits.

If you find this helpful, pleaase consider sharing the article.

An example of the above approach can be found in this demo code repository on GitHub.


Would you like to follow my posts on LinkedIn?