Tracking Snippet
Google's gtag.js is the most recent implementation of their analytics and tracking API. The default installation 'snippet' works well for traditional web pages, but requires some modification for single page applications
The global site tag (gtag.js) is a JavaScript tagging framework and API that allows you to send event data to Google Analytics, AdWords, and Doubleclick -- Google gtag Documentation
This is the official tracking snippet (as of April 2018), where GA_TRACKING_ID
is replaced with your site/product id:
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_TRACKING_ID"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_TRACKING_ID');
</script>
Calling gtag('config', 'UA-XXXXXXX-1')
sets the tracking id for all further events
from that page but also sends a pageview hit to Google Analytics. For SPA's, this can result in multiple (usually 2) page views being logged against the site when a page is loaded for the first time
IP anonymization
[Updated July 2019] Google Analytics does not make visitor IP information available to a website owner, but gtag.js
does send this information to Google Analytics. With the introduction of GDRP, Google have introduced an additional option to anonymize the IP addresses of hits sent to Google Analytics
gtag('config', '<GA_MEASUREMENT_ID>', { 'anonymize_ip': true });
Note: the
GA_TRACKING_ID
is now called theGA_MEASUREMENT_ID
in more recent documentation
Snippet For SPA
We can update the default snipped to prevent the initial gtag('config', 'UA-XXXXXXX-1')
call from sending a pageview hit by including { 'send_page_view': false }
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXX-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'UA-XXXXXXX-1', { 'send_page_view': false });
</script>
Page Navigation
When navigation events complete we send a pageview hit along with the URL Path we wish to register the call against. This will prevent duplication of page views being logged by analytics
gtag('config', 'UA-XXXXXXXX-1', { 'page_path': '/pages/page1' });
Angular Specific
It's advised (by whom I'm not sure, but lets just says it's advisable) to have an app wide structure for analytics event handling. Creating an analytics service and some helper functions to parse and trim the URL's would be a sensible approach
Analytics Service
google-gtag.service
import { Injectable } from '@angular/core';
import { SITE_CONFIG } from '../../SITE_CONFIG';
declare var gtag: Function;
@Injectable()
export class GoogleGtagService {
constructor() { }
sendPageView( path: string) {
gtag('config', SITE_CONFIG.GA_TRACKING_ID, { 'page_path': path });
}
}
URL Helper Function
If you send the entire URL path to analytics, paths with fragments or query params will be registered as unique hits. In most cases you will want to log page views against the primary path. Params and fragments could be sent as events, or as additional variables with the call. The following is an example helper function that returns only the root path of the primary Angular outlet
import { Router, PRIMARY_OUTLET, UrlSegmentGroup, UrlSegment } from '@angular/router';
....
/**
* Return the root url of the PRIMARY_OUTLET - no queryParams or fragment
* @param url
*/
getRootUrl(url: string) {
let retVal = '/';
let urlSegmentGroup: UrlSegmentGroup = this.router.parseUrl(url).root.children[PRIMARY_OUTLET];
if (urlSegmentGroup) {
retVal += urlSegmentGroup.toString();
}
return retVal;
}
Note: UrlSegmentGroup.toString() does not include a leading '/'. Add this to the URL if required as I have done above
Navigation End Handler
You probably already have a navigation handler - for restoring page scroll or something else - and whithin this you should call your GoogleGtagService
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd)
)
.subscribe(event => {
this.updateGoogleAnalytics(event as NavigationEnd);
this.navEndHandler(event as NavigationEnd);
});
private updateGoogleAnalytics(event: NavigationEnd) {
let fullURL = event.urlAfterRedirects;
let rootURL = this.urlHelper.getRootUrl(fullURL);
// check that page has changed
if (rootURL !== this.currentTrackedURL) {
this.currentTrackedURL = rootURL;
this.gtagService.sendPageView(this.currentTrackedURL);
}
}
If you don't want pageview hits during development and testing, you can wrap the sendPageView
call within a if(environment.production)
condition, making sure to import the environment
object from '/environments/environment';
as opposed to environment.prod';
- an easy mistake to make with auto imports!
Make sure to use the event.urlAfterRedirects
property of NavigationEnd - this is often different to the event.url
if you use any redirectTo
properties in your Routes
setup
The this.currentTrackedURL
check is important as updating query params or routing to page fragments can trigger navigation events for the same page multiple times