Angular: Dynamic google analytics tracking id for every environment

Maybe, it’s moderately difficult to integrate google analytics using statically a tracking id in your web app especially in a SPA. but what if your application can be deployed in multiple environments each with dedicated tracking id?

put-in

First, by default, google analytics tracks visited page based on page loading which can be a little different when your app is a single page application. This way, google analytics is not able to catch analytics since it is only a single loading.

Solutions are always everywhere. Angular provides a subscriber on route change which could be helpful to track inside the page view and give google analytics the hand to manage it. (we will see the code example below)

Problem context

If you are using angular with a docker image container and trying to inject tracking id from a config map as an environment variable, this how-to can be suitable for you.
This how-to is available also is you are using a google tag manager

Since you create a google analytics account, it provides a snippet of code to put in the page head tag

<head> … <!– Google Analytics –> <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-XXXXX-Y’, ‘auto’); ga(‘send’, ‘pageview’); </script> <!– End Google Analytics –> </head

The point here is that you can't access the value of the tracking id if you
set it in a setting or config variable. You are out of the Angular App
container.
R├ęsultat de recherche d'images pour "let's go troll"

we suppose that the image can read the config injected by K8S

FROM node:10.15.0 as builder
...
ENV MYFM__GA__TRACKING_ID=UA-324563245-2

CMD ["/usr/share/nginx/startup.sh"
...

declaring an env variable, and then calling a sh to inject the env variables available in the JSON file that will be loaded by my app.

#!/bin/sh
set -e

envsubst < path/settings.template.json > path/settings.json
exec nginx -g 'daemon off;'

the setting.template.json is a JSON template that will be transformed to the setting.json containing the real value from the env var.

//settings.template.json
{
  "gaTrackingID": "${MYFM__GA__TRACKING_ID}"
}
//after executing the sh 

//settings.json
{
  "gaTrackingID": "UA-324563245-2"
}

Until now, we need just to read the JSON file and load it in the App bootstrap.
Let’s first create a service that will load the JSON and init a map config

export class AppSettings {
  public static gaTrackingID;
}
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {AppSettings} from '../app.settings';

@Injectable({
  providedIn: 'root'
})
export class AppConfigService {

  constructor(private http: HttpClient) { }

  loadAppConfig() {
    return this.http
      .get('assets/settings.json')
      .toPromise()
      .then((data: any) => {
        for (const propName in data) {
          if (data.hasOwnProperty(propName)) {
            AppSettings[propName] = data[propName];
          }
        }
      });
  }

}

The AppSettings now contain gaTrackingID with the value given from the setting.json

In your module, you need to call the method loadAppcconfig for the APP_INITIALIZER built-in provider.

const initializeAppConfigFactory = (appConfig: AppConfigService) => {
  return () => {
    return appConfig.loadAppConfig();
  };
};
@NgModule({
  imports: [
    ...
    CommonModule,
    HttpClientModule
  ],
  providers: [
     ...,
    {
      provide: APP_INITIALIZER,
      useFactory: initializeAppConfigFactory,
      multi: true,
      deps: [AppConfigService],
    }
    
  ]
})

Actually, the angular app knows your tracking id, it still demonstrates how to add the google analytics in the index.html (the head tag as recommended by google) depending on the environment where the application is deployed.

Let’s create a service contain a static method that adds the script tag in the dom manually using the tracking id available in the angular app.

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

@Injectable()
export class GoogleAnalyticsService {

  constructor() { }

  /**
   * load analytics
   * @param trackingID
   */
  static loadGoogleAnalytics(trackingID: string): void {
    const gaScript1 = document.createElement('script');
    gaScript1.innerText = `window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;ga(\'create\', \'${ trackingID }\', \'auto\');`;

    const gaScript = document.createElement('script');
    gaScript.setAttribute('async', 'true');
    gaScript.setAttribute('src', 'https://www.google-analytics.com/analytics.js');

    document.documentElement.firstChild.appendChild(gaScript1);
    document.documentElement.firstChild.appendChild(gaScript);
  }

}

As shown, we are adding the given snippet manually in the dom.

Finally, we just need to call the method the app component

import {Component, OnInit} from '@angular/core';
import {NavigationEnd, Router} from '@angular/router';
import {AppSettings} from './core/app.settings';
import {GoogleAnalyticsService} from './core/services/google-analytics.service';


@Component({
  selector: 'myscm-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {
  constructor(public router: Router ) {
    GoogleAnalyticsService.loadGoogleAnalytics(AppSettings.gaTrackingID);
    // this is the snippet which make google analytics able to track page view load as i mentioned above in the introduction
this.router.events.subscribe(event => {
        if (event instanceof NavigationEnd) {
          (window as any).ga('set', 'page', event.urlAfterRedirects);
          (window as any).ga('send', 'pageview');
        }
      }
    );
  }

  ngOnInit(): void {}
}

You can test it by inspecting the HTML in the browser the check if script tags exist.

NB: we haven’t modified or add anything in the index.html

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My app</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <myscm-root></myscm-root>
</body>
</html>

In case you was to track event on your app , feel free to add a new function in your service :

  public sendAnalyticsEvent(eventCategory: string, eventAction: string, eventLabel: string = null) {
    (window as any).ga('send', 'event', eventCategory, eventAction, eventLabel);
  }

I hope this tutorial was very helpful to you.

Leave a Reply

Your email address will not be published. Required fields are marked *