Tracking Google Analytics Page Views in Angular2

JavascriptGoogle AnalyticsAngular

Javascript Problem Overview


I have built a new site using Angular 2 as the front-end. As everything is done via push state, there are no page loads which typically trigger the Google Analytics code to send a page view to Google's servers.

How can I manually send page view events to Google so that I can track what users of my site are viewing?

Javascript Solutions


Solution 1 - Javascript

I managed to get this working by subscribing to changes on the router, checking that the route had actually changed (I was getting multiple events on some routes at times) and then sending the new path to Google.

app.component.ts

import { ... } from '...';

// Declare ga function as ambient
declare var ga:Function;

@Component({ ... })

export class AppComponent {
    private currentRoute:string;

    constructor(_router:Router) {
        // Using Rx's built in `distinctUntilChanged ` feature to handle url change c/o @dloomb's answer
        router.events.distinctUntilChanged((previous: any, current: any) => {
            // Subscribe to any `NavigationEnd` events where the url has changed
            if(current instanceof NavigationEnd) {
                return previous.url === current.url;
            }
            return true;
        }).subscribe((x: any) => {
            ga('set', 'page', x.url);
            ga('send', 'pageview')
        });
      }
    }
}

You also need to include the google analytics code in your main index file before loading your angular2 app so that the global ga object exists, but you don't want to send the initial view twice. In order to do this, remove the following line from the GA script

index.html

<script>
  (function(i,s,o,g,r,a,m){...})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-XXXXXXXX-X', 'auto');
  // Remove this line to avoid sending the first page view twice.
  //ga('send', 'pageview');

</script>
<!-- 
    Load your ng2 app after ga. 
    This style of deferred script loading doesn't guarantee this will happen
    but you can use Promise's or what works for your particular project. 
-->
<script defer type="text/javascript" src="/app.js"></script>

Using a third party library

As an alternative to implementing GA yourself, the library Angulartics2 is also a popular tool for implementing GA tracking and also integrates with other analytics vendors as well.

Solution 2 - Javascript

Expanding on Ian's answer. You can use Rx's built in features to handle the distinction between current and new routes.

import { NavigationEnd, Router } from '@angular/router';

declare var ga: any;

export class AppComponent {
    	constructor(public router: Router) {
    		router.events.distinctUntilChanged((previous: any, current: any) => {
    			if(current instanceof NavigationEnd) {
    				return previous.url === current.url;
    			}
    			return true;
    		}).subscribe((x: any) => {
    			console.log('router.change', x);
    			ga('send', 'pageview', x.url);
    		});
    	}
    }

We are using the distinctUntilChanged operator to make the observer only emit items that are of type NavigationEnd and do not have the same route as the previously emitted item.

Solution 3 - Javascript

If you are running into this issue after August 2017 then you most probably should use gtag.js (Google Universal Analytics Global Site Tag) instead of the old analytics.js. I suggest you to check the difference between the both in Migrate from analytics.js to gtag.js page, as well as How gtag.js works in Single page applications before continuing.

When you get your code snippet from Google Analytics it looks like this:

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=<%= GOOGLE_ANALYTICS_ID %>"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', '<%= GOOGLE_ANALYTICS_ID %>'); <!-- Remove that one -->
</script>

You need to remove the last line of the script and add the rest to your index.html.

Then you have to add the line you deleted from the script above to your code and add the page to track. Basically it's almost the same as the guys above suggested for analytics.js but now you use the gtag.js function.

For example if you want to track all pages you open here is the sample code:

import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import 'rxjs/add/operator/distinctUntilChanged';

// This still has to be declared
declare var gtag: Function;

@Component({
    moduleId: module.id,
    selector: 'my-app',
    templateUrl: 'app.component.html',
    styleUrls: ['app.component.css'],
})
export class AppComponent implements OnInit {

    constructor(private router: Router) { }

    ngOnInit() {
        this.router.events.distinctUntilChanged((previous: any, current: any) => {
            // Subscribe to any `NavigationEnd` events where the url has changed
            if(current instanceof NavigationEnd) {
                return previous.url === current.url;
            }
            return true;
        }).subscribe((x: any) => {
            gtag('config', '<%= GOOGLE_ANALYTICS_ID %>', {'page_path': x.url});
        });
    }
}

If you've read the documentation for the gtag.js then you know that there could be tons of tracking options, but I focus on the most basic usage here.

Solution 4 - Javascript

In Angular 6, I suggest for the app.component.ts:

import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router'
import { Title } from '@angular/platform-browser';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})

export class AppComponent {

  constructor(
    private router: Router,
    private titleService: Title
  ){ }

  ngOnInit() {
     this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        (<any>window).gtag('config', '<%= GOOGLE_ANALYTICS_ID %>', {
          'page_title' : this.titleService.getTitle(),
          'page_path': event.urlAfterRedirects
        });
      }
    });
  }

}

For the index.html :

  <!-- Global site tag (gtag.js) - Google Analytics -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=<%= GOOGLE_ANALYTICS_ID %>"></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag() { dataLayer.push(arguments); }
    gtag('js', new Date());
  </script>

You could manage the title of your pages with the Title service provided by Angular : https://angular.io/guide/set-document-title

Solution 5 - Javascript

Assuming that every Angular Route has its own title in app.routing.ts:

   {
    path: 'shop',
    component: ShopComponent,
    data: {
      title: ' == This is Shop Component Title =='
    },
    canActivate: [AuthGuard]
  },

Previously mentioned solutions will still display the same page title for each route on Google Analytics Report. In order to make use of corresponding Angular Route titles ( instead of index.html <title> tag content all the time), use the code below in app.component.ts

  this.router.events.subscribe(event => {

  if (event instanceof NavigationEnd) {
    (<any>window).ga('set', 'page', event.urlAfterRedirects);

    // ----------
    //use the following 3 lines of code to use
    //correnct titles for routes        
    // ----------

    let currentRoute = this.route.root;
    let title = this.getPageTitle(currentRoute);
    (<any>window).ga('set', 'title', title);

    (<any>window).ga('send', 'pageview');

  }
});

...where getPageTitle method is as follows:

getPageTitle = function (currentRoute: ActivatedRoute) {
  let data;
    do {
      const childrenRoutes = currentRoute.children;
      currentRoute = null;
      childrenRoutes.forEach(route => {

      if (route.outlet === 'primary') {
        currentRoute = route;
        data = route.snapshot.data;
      }
    });
  } while (currentRoute);
  return data.title;
};

Note: This solution applies to Anguler 5 and below. In Angular 6, you can also use TitleService

Solution 6 - Javascript

this.router.events.subscribe(event => {
    if (event instanceof NavigationEnd) {
        ga('set','page', event.urlAfterRedirects);
        ga('send', 'pageview');
    }
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIan BelcherView Question on Stackoverflow
Solution 1 - JavascriptIan BelcherView Answer on Stackoverflow
Solution 2 - JavascriptdloombView Answer on Stackoverflow
Solution 3 - JavascriptnyxzView Answer on Stackoverflow
Solution 4 - JavascriptCyril BlanchetView Answer on Stackoverflow
Solution 5 - JavascriptOmer GurarslanView Answer on Stackoverflow
Solution 6 - JavascriptTony Muchui BlaxxView Answer on Stackoverflow