loading...

Using Google Analytics with Angular

jordanirabor profile image Jordan Irabor Updated on ・5 min read

Google Analytics is known to be very efficient and easy to integrate into web applications. Once installed, it will send detailed information about the navigation history of visitors on the website.

Installing Google Analytics for traditional web applications is as easy as copying and pasting the JavaScript tracking snippet provided on registration of a new property. The only setup necessary is adding the snippet to the <head> tag of the HTML files to be tracked and the entire navigation history of each visitor will be updated in real time.

For SPAs, this is not the case, setting up Google Analytics requires a little more effort.

Do not get lost in the comfort of pasting snippets around because the era of single page applications is forcing developers and non-developers alike to adopt new approaches towards implementing web analytics services on their applications.

Why are SPAs so different?

The JavaScript tracking snippet works well with traditional web applications because whenever a new page is loaded, the snippet is freshly run; hence Google Analytics information is updated for that page. For a SPA where no actual full-page request is made, the analytics.js code will run only once and user navigation information will not be tracked correctly.

In this article, we will write our application to inform Google Analytics on the successful load of every new page and send the correct page information.

Angular is an awesome JavaScript framework for developing single page applications; it has a robust routing framework called the UI-Router that is responsible for updating the view based on the state of the application, this article will mainly discuss how Angular applications can be intentionally written to support Google Analytics services.

The source code is available here on GitHub.

Below is a step by step guide:

Setting up Google Analytics

We begin by setting up a Google Analytics account and registering a property here.

If you have Angular and other modules installed on your local machine, be sure to have the UI-Router module also available because we will be injecting it as a module dependency when initializing our app's main module.

If you'd rather use Angular and other modules from another computer, you can get Angular on this URL and the UI-Router module on this URL (For the sake of this article, I will be referencing Angular and its modules from a CDN).

Setting up the head tag

Copy and paste the JavaScript tracking snippet with its unique tracking ID just above the closing </head> tag.

Now comment out the two lines of code that look like this:

ga('create', 'UA-XXXXX-X', 'auto');

ga('send', 'pageview');

Where UA-XXXXX-X is your unique tracking ID.

We are commenting these lines out right now because we are building a SPA and not a traditional application, we'd later wrap them in the run block of our Angular script to ensure that when our app begins running, they will only be triggered based on certain changes in the application's state.

Following the above instructions, we should have a </head> tag looking similar to this:

<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.3/angular-ui-router.js"></script>
<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');
</script>
</head>
</html>

Setting up the body as a view container

First things first, let's understand how the view section of a SPA works; since the entire application behaves as if it is a single page, Angular loads the content of other requested pages into the view section. We can think of this view section as a container within the main application. To help Angular indentify the view section in our markup, we assign the ui-view directive of the UI-Router module to a div tag, just like this:

<body ng-app="myApp">
<div ui-view>
<a ui-sref="first">This is the first page.</a>
<a ui-sref="second">This is the second page.</a>
<a ui-sref="third">This is the third page.</a>
<h1>{{ title }}</h1>
</div>
</body>

This body section above looks simple but it defines the basic structure for defining the Angular app and the view section.

Configuring the application

We begin by initializing the main Angular module and injecting UI-Router as a module dependency.

var app = angular.module("myApp", ['ui.router']);

Next we write the configuration block of our application and inject all the providers we will be needing for this application, the result looks like this:

app.config(['$urlRouterProvider', '$stateProvider', function($urlRouterProvider, $stateProvider){
      $urlRouterProvider.otherwise('/');
      $stateProvider
      .state('index', {
            url: '/',
            templateUrl: 'index.html',
            controller: function($scope){
            $scope.title = 'I am the root page, ergo, submit thyself to me.'
    }
  })

Again, this is just a simple demonstration but it defines how an actual application should be set up.

Lastly, we write the run block of the Angular app to ensure that our application sends data to Google Analytics whenever a new page is loaded into the view section. We do this by injecting some services and using their methods during the run phase of our app's lifecycle.

We inject the $location service and it will help us determine the current location of the browser using its path() method.

Remember the two lines of code we commented out above? We'll be needing them very soon since we've gotten to the run block we spoke about earlier.

Next, we inject the global $window service and what this will do for us is:- Trigger the ga's (google analytics object) methods we commented out in the </head> of our program.

Next, we inject the $transitions service that will help us determine when a user has made or is making a transition to a new state.

In the end, we combine all these services and their methods to jointly instruct Angular to send data (containing information about the browsers current location) to Google Analytics when a transition to a new state is successful.

Here is what the code for the run block looks like:

app.run(['$location', '$window','$transitions', function($location, $window, $transitions){
        $window.ga('create', 'UA-XXXXX-X', 'auto');
        $transitions.onSuccess({}, function(){
              $window.ga('send', 'pageview', $location.path());
        });
}])

Awesome, now for every transition to a new state in our application, we'd get an update on our Google Analytics dashboard. On my web browser, I have visited my application and have navigated to a view that has a URL of /first.

This is the immediate update on my Google Analytics dashboard:

Conclusion

Just like that, our application is now being tracked correctly by Google Analytics, we get a real-time update on the navigation history of visitors to our web application. It requires just a little modification but goes a long way in ensuring that we stay in control and know our users!

Posted on by:

jordanirabor profile

Jordan Irabor

@jordanirabor

A software engineer sitting in front of two desks and a mechanical keyboard. I build software at [Stears]('https://stearsng.com') and write tutorials for Pusher and LogRocket.

Discussion

markdown guide
 

Nice example.

As a bonus do NOT implement GA or any other analytics directly in your business logic, always make a relay (abstract class, event system, pub/sub, anything) and encapsulate your tracking there. In 100% of production projects I saw this problem.

The magic rule: "when you want to add a new tracking system there should be only 1 part of the code that will be modified". Keeping the "1 reason to change" rule.

Even if you use a service like segment you may realize that is too expensive and want to switch to other one.