Angular and Google Analytics
Tags: angular
Published: 2020-08-25 05:12:20

---
title: Angular and Google Analytics
published: true
date: 2020-08-24 06:00:00 UTC
tags: angular
canonical_url: https://www.prestonlamb.com/blog/angular-and-google-analytics
---

## [tldr;](#tldr)

There are many times when we need to insert Google Analytics into our application to track what a user does or where they go in the application. Single page apps like Angular work differently, though, than a static site for example. This post will show you how to create a service that loads Google Analytics and sets up page view tracking for you.

## [Getting Started](#getting-started)

Before starting this post, you should have gone to Google Analytics and set up a project. I’m not going to cover that in this blog post, but there are many tutorials out there to show you what to do. Once you have your project set up, you should be given a code snippet to inject into your website. It will look something like this:

```html
<script src="https://www.googletagmanager.com/gtag/js?id=XX-XXXXXXXX-X"></script>
<script>
	window.dataLayer = window.dataLayer || [];
	function gtag() {
		dataLayer.push(arguments);
	}
	gtag('js', new Date());
	gtag('config', 'XX-XXXXXXXX-X');
</script>
```

The ‘XX-XXXXXXXX-X’ is the ID of the analytics site that will have been given to you when setting up analytics. Once you have all this information, you’re ready to go on to the next step.

## [Google Analytics Service](#google-analytics-service)

If we were building a static site, one that reloaded the site each time you navigated from page to page, then we’d just take the code from above and put it in the `head` section of each page. But single page applications don’t work like that. The index.html file of an Angular app is loaded just once, and then all the content of the page is dynamically swapped out when the user clicks on different links. So we have to do our page view tracking a little different.

Let’s start by creating a service that will manage all our Google Analytics functionality:

```bash
ng g s google-analytics
```

In this service, we need to load the scripts that Google Analytics gave us, and that I referenced above in the Getting Started section. We’ll make a couple private functions that do the setup, and a single `init` function that will be called from our main `AppComponent`. Before showing that, though, we need to take the second `script` from above (minus the `gtag('config', 'XX-XXXXXXXX-X')` part) and put it in a separate `.js` file. So that file will look like this:

```js
// google-analytics-script.js
window.dataLayer = window.dataLayer || [];
function gtag() {
	dataLayer.push(arguments);
}
gtag('js', new Date());
```

Add it to the `assets` array for your app in the `angular.json` file:

```json
<!-- angular.json -->
{
  ...
  "build": {
    "options": {
      "assets": ["path/to/google-analytics-script.js"]
    }
  }
}
```

Okay, so now that we have that second part of the Google Analytics script in a `.js` file that we can load, let’s take a look at our service:

```ts
// google-analytics.service.ts
declare let gtag: Function;

export class GoogleAnalyticsService {
  private googleAnalyticsId: string;
  private renderer2: Renderer2;
  private scriptsLoaded: boolean = false;

  constructor(
    private rendererFactory2: RendererFactory2,
    @Inject(DOCUMENT) private _document: Document,
    private _config: RuntimeConfigLoaderService,
    private _router: Router,
  ) {
    this.renderer2 = this.rendererFactory2.createRenderer(null, null);
    this.googleAnalyticsId = this._config.getConfigObjectKey('googleAnalyticsId');
  }

  init() {
    if (!this.scriptsLoaded) {
      this.insertMainScript();
    }
  }

  private insertMainScript() {
    if (this.googleAnalyticsId) {
      const script: HTMLScriptElement = this.renderer2.createElement('script');
      script.type = 'text/javascript';
      script.onload = this.insertSecondHalfOfScript.bind(this);
      script.src = `https://www.googletagmanager.com/gtag/js?id=${this.googleAnalyticsId}`;
      script.text = '';
      this.renderer2.appendChild(this._document.body, script);
    }
  }

  private insertSecondHalfOfScript() {
    const script: HTMLScriptElement = this.renderer2.createElement('script');
    script.type = 'text/javascript';
    script.src = '/path/to/google-analytics-script.js';
    script.text = '';
    this.renderer2.appendChild(this._document.body, script);
    script.onload = () => {
      this.scriptsLoaded = true;
    };
  }
}
```

Let’s break this down. First, we need to declare `gtag` outside the class so that we can call it later on. Next, we inject `RendererFactory2`, `DOCUMENT`, `Router`, and `RuntimeConfigLoaderService` into this service. You don’t have to use [`RuntimeConfigLoaderService`](https://www.npmjs.com/package/runtime-config-loader) if you don’t want to, but this way you can easily change the Google Analytics ID without touching the service. In the constructor or the service, we create an instance of `Renderer2` which we will use to load the scripts. We also store the Google Analytics ID from the configuration.

```ts
// google-analytics.service.ts
constructor(
  private rendererFactory2: RendererFactory2,
  @Inject(DOCUMENT) private _document: Document,
  private _config: RuntimeConfigLoaderService,
  private _router: Router,
) {
  this.renderer2 = this.rendererFactory2.createRenderer(null, null);
  this.googleAnalyticsId = this._config.getConfigObjectKey('googleAnalyticsId');
}
```

Next up we create two private functions that will actually load the scripts, and then a public `init` function that can be called from the `AppComponent`:

```ts
// google-analytics.service.ts
init() {
  if (!this.scriptsLoaded) {
    this.insertMainScript();
  }
}

private insertMainScript() {
  if (this.googleAnalyticsId) {
    const script: HTMLScriptElement = this.renderer2.createElement('script');
    script.type = 'text/javascript';
    script.onload = this.insertSecondHalfOfScript.bind(this);
    script.src = `https://www.googletagmanager.com/gtag/js?id=${this.googleAnalyticsId}`;
    script.text = '';
		 
   this.renderer2.appendChild(this._document.body, script);
  }
}

private insertSecondHalfOfScript() {
  const script: HTMLScriptElement = this.renderer2.createElement('script');
  script.type = 'text/javascript';
  script.src = '/path/to/google-analytics-script.js';
  script.text = '';
  this.renderer2.appendChild(this._document.body, script);
  script.onload = () => {
    this.scriptsLoaded = true;
  };
}
```

In the `init` function, we only call the `insertMainScript` function if the scripts have not been loaded. The `insertMainScript` function only runs if we have a `googleAnalyticsId`. We create a `script` element with `Renderer2`, and set the `type` and `src` attributes. We also tell it to call a function, `insertSecondHalfOfScript`, after this first script is loaded. Then we append the newly created `script` to the document.

In the second function, we load the file we created above, `google-analytics-script.js`. Once it has loaded, we run an arrow function and set the `scriptsLoaded` variable to true.

With these three functions created, your app is now ready to load the required Google Analytics Scripts. In your main `AppComponent`, inject this new `GoogleAnalyticsService` and call the `init` method from `ngOnInit`:

```ts
export class AppComponent implements OnInit {
  constructor(..., private _analytics: GoogleAnalyticsService) {}

  ngOnInit() {
    this._analytics.init();
  }
}
```

## [Tracking Page Views](#tracking-page-views)

So our service is working and loading Google Analytics for us. But we still need to get it tracking page views. To do this, we need to use the Angular Router and call a Google Analytics function to track the navigation event. We’ll do that like this, adding a function to our `GoogleAnalyticsService`:

```ts
// google-analytics.service.ts
trackSinglePageView(event: NavigationEnd) {
  if (this.googleAnalyticsId && this.scriptsLoaded) {
    gtag('config', this.googleAnalyticsId, { page_path: event.urlAfterRedirects });
  }
}

trackPageViews() {
  return this._router.events.pipe(
    filter(() => this.scriptsLoaded === true),
    filter((evt: RouterEvent) => evt instanceof NavigationEnd),
    tap((event: NavigationEnd) => {
      this.trackSinglePageView(event);
    }),
  );
}
```

The `trackPageViews` function is the one that we need to subscribe to to make sure page views are logged to Google Analytics. Lets cover what it’s doing real quick though. First, we’re using the `events` observable stream from the Angular router. Inside the `pipe`, we use two `filter` operators. The first one will make sure that our scripts are loaded before we try and track anything. The second `filter` operator makes sure that we only continue if the current event is a `NavigationEnd` event. We only want to report anything to Google Analytics if the router is done routing. Finally, we use the `tap` operator to call a function which will send the event to Google Analytics. You could just report to Google Analytics in the `tap` operator, but the upside to this is that you could call `trackSinglePageView` from anywhere, if needed.

Back in our `AppComponent`, we just need to subscribe to the observable that’s returned from the `trackPageViews` function in `ngOnInit`:

```ts
export class AppComponent implements OnInit {
  constructor(..., private _analytics: GoogleAnalyticsService) {}

  ngOnInit() {
    this._analytics.init();
    this._analytics.trackPageViews().subscribe();
  }
}
```

With that, our app will start reporting each page view to Google Analytics.

## [Tracking Other Events](#tracking-other-events)

If you need to track other events using Google Analytics, just add the following function to the `GoogleAnalyticsService`:

```ts
trackEvent(
  { eventName, eventCategory, eventAction, eventLabel, eventValue } = {
    eventName: null,
    eventCategory: null,
    eventAction: null,
    eventLabel: null,
    eventValue: null,
  },
) {
  gtag('event', eventName, {
    eventCategory,
    eventLabel,
    eventAction,
    eventValue,
  });
}
```

This function uses [named parameters](/blog/javascript-functions#named-parameters-to-functions), but all you need to do is pass an event name, category, action, label, and value to the function. It will then pass that event on to Google Analytics. You can call this function from anywhere in your app, any time a user does something you want to track.

## [Conclusion](#conclusion)

Overall, it was easier to add Google Analytics to my site than I thought. It took a little more time to add it all into a service, making sure that the scripts were loaded before doing anything else, but this way I didn’t have to edit the `index.html` directly for the app. Also, because the ID is an environment variable I now have one ID for our QA environment and one ID for production. If I was editing the `index.html` file directly, things would hae been more complicated. This is an especially useful way of doing things in an [NX workspace](/blog/why-use-nx-for-monorepos), which is where I implemented this. Now with just a couple lines of code and adding a Google Analytics ID, my apps can have Google Analytics tracking.

You can view the entire service in this [gist](https://gist.github.com/pjlamb12/bec2e56d257d895770496a9074beab0e).