I'm setting up a new app using AngularJS as the frontend. Everything on the client side is done with HTML5 pushstate and I'd like to be able to track my page views in Google Analytics.
If you're using ng-view in your Angular app you can listen for the $viewContentLoaded event and push a tracking event to Google Analytics.
Assuming you've set up your tracking code in your main index.html file with a name of var _gaq and MyCtrl is what you've defined in the ng-controller directive.
function MyCtrl($scope, $location, $window) {
$scope.$on('$viewContentLoaded', function(event) {
$window._gaq.push(['_trackPageView', $location.url()]);
});
}
UPDATE:
for new version of google-analytics use this one
function MyCtrl($scope, $location, $window) {
$scope.$on('$viewContentLoaded', function(event) {
$window.ga('send', 'pageview', { page: $location.url() });
});
}
When a new view is loaded in AngularJS, Google Analytics does not count it as a new page load. Fortunately there is a way to manually tell GA to log a url as a new pageview.
_gaq.push(['_trackPageview', '<url>']); would do the job, but how to bind that with AngularJS?
Here is a service which you could use:
(function(angular) {
angular.module('analytics', ['ng']).service('analytics', [
'$rootScope', '$window', '$location', function($rootScope, $window, $location) {
var track = function() {
$window._gaq.push(['_trackPageview', $location.path()]);
};
$rootScope.$on('$viewContentLoaded', track);
}
]);
}(window.angular));
When you define your angular module, include the analytics module like so:
angular.module('myappname', ['analytics']);
UPDATE:
You should use the new Universal Google Analytics tracking code with:
$window.ga('send', 'pageview', {page: $location.url()});
app.run(function ($rootScope, $location) {
$rootScope.$on('$routeChangeSuccess', function(){
ga('send', 'pageview', $location.path());
});
});
Just a quick addition. If you're using the new analytics.js, then:
var track = function() {
ga('send', 'pageview', {'page': $location.path()});
};
Additionally one tip is that google analytics will not fire on localhost. So if you are testing on localhost, use the following instead of the default create (full documentation)
ga('create', 'UA-XXXX-Y', {'cookieDomain': 'none'});
I've created a service + filter that could help you guys with this, and maybe also with some other providers if you choose to add them in the future.
Check out https://github.com/mgonto/angularytics and let me know how this works out for you.
Merging the answers by wynnwu and dpineda was what worked for me.
angular.module('app', [])
.run(['$rootScope', '$location', '$window',
function($rootScope, $location, $window) {
$rootScope.$on('$routeChangeSuccess',
function(event) {
if (!$window.ga) {
return;
}
$window.ga('send', 'pageview', {
page: $location.path()
});
});
}
]);
Setting the third parameter as an object (instead of just $location.path()) and using $routeChangeSuccess instead of $stateChangeSuccess did the trick.
Hope this helps.
I've created a simple example on github using the above approach.
https://github.com/isamuelson/angularjs-googleanalytics
The best way to do this is using Google Tag Manager to fire your Google Analytics tags based on history listeners. These are built in to the GTM interface and easily allow tracking on client side HTML5 interactions .
Enable the built in History variables and create a trigger to fire an event based on history changes.
In your index.html, copy and paste the ga snippet but remove the line ga('send', 'pageview');
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID -->
<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','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-XXXXXXXX-X');
</script>
I like to give it it's own factory file my-google-analytics.js with self injection:
angular.module('myApp')
.factory('myGoogleAnalytics', [
'$rootScope', '$window', '$location',
function ($rootScope, $window, $location) {
var myGoogleAnalytics = {};
/**
* Set the page to the current location path
* and then send a pageview to log path change.
*/
myGoogleAnalytics.sendPageview = function() {
if ($window.ga) {
$window.ga('set', 'page', $location.path());
$window.ga('send', 'pageview');
}
}
// subscribe to events
$rootScope.$on('$viewContentLoaded', myGoogleAnalytics.sendPageview);
return myGoogleAnalytics;
}
])
.run([
'myGoogleAnalytics',
function(myGoogleAnalytics) {
// inject self
}
]);
I found the gtag() function worked, instead of the ga() function.
In the index.html file, within the <head> section:
<script async src="https://www.googletagmanager.com/gtag/js?id=TrackingId"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'TrackingId');
</script>
In the AngularJS code:
app.run(function ($rootScope, $location) {
$rootScope.$on('$routeChangeSuccess', function() {
gtag('config', 'TrackingId', {'page_path': $location.path()});
});
});
Replace TrackingId with your own Tracking Id.
If someone wants to implement using directives then, identify (or create) a div in the index.html (just under the body tag, or at same DOM level)
<div class="google-analytics"/>
and then add the following code in the directive
myApp.directive('googleAnalytics', function ( $location, $window ) {
return {
scope: true,
link: function (scope) {
scope.$on( '$routeChangeSuccess', function () {
$window._gaq.push(['_trackPageview', $location.path()]);
});
}
};
});
For those of you using AngularUI Router instead of ngRoute can use the following code to track page views.
app.run(function ($rootScope) {
$rootScope.$on('$stateChangeSuccess', function (event, toState, toParams, fromState, fromParams) {
ga('set', 'page', toState.url);
ga('send', 'pageview');
});
});
If you're using ui-router you can subscribe to the $stateChangeSuccess event like this:
$rootScope.$on('$stateChangeSuccess', function (event) {
$window.ga('send', 'pageview', $location.path());
});
For a complete working example see this blog post
Use GA 'set' to ensure routes are picked up for Google realtime analytics. Otherwise subsequent calls to GA will not show in the realtime panel.
$scope.$on('$routeChangeSuccess', function() {
$window.ga('set', 'page', $location.url());
$window.ga('send', 'pageview');
});
Google strongly advises this approach generally instead of passing a 3rd param in 'send'.
https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications
Developers creating Single Page Applications can use autotrack, which includes a urlChangeTracker plugin that handles all of the important considerations listed in this guide for you. See the autotrack documentation for usage and installation instructions.
I am using AngluarJS in html5 mode. I found following solution as most reliable:
Use angular-google-analytics library. Initialize it with something like:
//Do this in module that is always initialized on your webapp
angular.module('core').config(["AnalyticsProvider",
function (AnalyticsProvider) {
AnalyticsProvider.setAccount(YOUR_GOOGLE_ANALYTICS_TRACKING_CODE);
//Ignoring first page load because of HTML5 route mode to ensure that page view is called only when you explicitly call for pageview event
AnalyticsProvider.ignoreFirstPageLoad(true);
}
]);
After that, add listener on $stateChangeSuccess' and send trackPage event.
angular.module('core').run(['$rootScope', '$location', 'Analytics',
function($rootScope, $location, Analytics) {
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams, options) {
try {
Analytics.trackPage($location.url());
}
catch(err) {
//user browser is disabling tracking
}
});
}
]);
At any moment, when you have your user initalized you can inject Analytics there and make call:
Analytics.set('&uid', user.id);
I am using ui-router and my code looks like this:
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams){
/* Google analytics */
var path = toState.url;
for(var i in toParams){
path = path.replace(':' + i, toParams[i]);
}
/* global ga */
ga('send', 'pageview', path);
});
This way I can track different states. Maybe someone will find it usefull.
I personally like to set up my analytics with the template URL instead of the current path. This is mainly because my application has many custom paths such as message/:id or profile/:id. If I were to send these paths, I'd have so many pages being viewed within analytics, it would be too difficult to check which page users are visiting most.
$rootScope.$on('$viewContentLoaded', function(event) {
$window.ga('send', 'pageview', {
page: $route.current.templateUrl.replace("views", "")
});
});
I now get clean page views within my analytics such as user-profile.html and message.html instead of many pages being profile/1, profile/2 and profile/3. I can now process reports to see how many people are viewing user profiles.
If anyone has any objection to why this is bad practise within analytics, I would be more than happy to hear about it. Quite new to using Google Analytics, so not too sure if this is the best approach or not.
I suggest using the Segment analytics library and following our Angular quickstart guide. You’ll be able to track page visits and track user behavior actions with a single API. If you have an SPA, you can allow the RouterOutlet component to handle when the page renders and use ngOnInit to invoke page calls. The example below shows one way you could do this:
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
ngOnInit() {
window.analytics.page('Home');
}
}
I’m the maintainer of https://github.com/segmentio/analytics-angular. With Segment, you’ll be able to switch different destinations on-and-off by the flip of a switch if you are interested in trying multiple analytics tools (we support over 250+ destinations) without having to write any additional code. 🙂
Merging even more with Pedro Lopez's answer,
I added this to my ngGoogleAnalytis module(which I reuse in many apps):
var base = $('base').attr('href').replace(/\/$/, "");
in this case, I have a tag in my index link:
<base href="/store/">
it's useful when using html5 mode on angular.js v1.3
(remove the replace() function call if your base tag doesn't finish with a slash /)
angular.module("ngGoogleAnalytics", []).run(['$rootScope', '$location', '$window',
function($rootScope, $location, $window) {
$rootScope.$on('$routeChangeSuccess',
function(event) {
if (!$window.ga) { return; }
var base = $('base').attr('href').replace(/\/$/, "");
$window.ga('send', 'pageview', {
page: base + $location.path()
});
}
);
}
]);
If you are looking for full control of Google Analytics's new tracking code, you could use my very own Angular-GA.
It makes ga available through injection, so it's easy to test. It doesn't do any magic, apart from setting the path on every routeChange. You still have to send the pageview like here.
app.run(function ($rootScope, $location, ga) {
$rootScope.$on('$routeChangeSuccess', function(){
ga('send', 'pageview');
});
});
Additionaly there is a directive ga which allows to bind multiple analytics functions to events, like this:
In Google's documentation it is said that an event can be tracked in the following way:
<a onclick="_gaq.push(['_trackEvent', 'category', 'action', 'opt_label', opt_value]);">click me</a>
or older version:
<a onclick="pageTracker._trackEvent('category', 'action', 'opt_label', opt_value);">click me</a>
I was looking with Firebug to the request that are made when a click on a link and I see there aborted request:
http://www.google-analytics.com/__utm.gif?utmwv=4.7.2&utmn=907737223&....
This happens because browser unload all javascript when user navigates to a new page. How in this case event tracking is performed?
Edit:
Since one picture can be worth a thousand words...
When I click a link firebug shows me this sequence of requests (here are shown first four, after follows requests to fill page content)
The problem is that there isn't enough time for the script to finish running before the user is taken to the next page. What you can do is create a wrapper function for your GA code and in the onclick, call the wrapper function and after the GA code is triggered in your wrapper function, set a time out and update location.href with the link's url. Example:
click me
<script type='text/javascript'>
function wrapper_function(that,category,action,opt_label,opt_value) {
_gaq.push(['_trackEvent', category, action, opt_label, opt_value]);
window.setTimeout("window.location.href='" + that.href + "'", 1000);
}
</script>
code will vary a bit based on your link but hopefully you get the idea - basically it waits a little bit before taking the user to the target url to give the script some time to execute.
Update:
This answer was posted several years ago and quite a lot has happened since then, yet I continue to get feedback (and upvotes) occasionally, so I thought I'd update this answer with new info. This answer is still doable but if you are using Universal Analytics then there is a hitCallback function available. The hitCallback function is also available to their traditional _gaq (ga.js) but it's not officially documented.
This problem is answered in Google's documentation:
use
<script type="text/javascript">
function recordOutboundLink(link, category, action) {
try {
var myTracker=_gat._getTrackerByName();
_gaq.push(['myTracker._trackEvent', ' + category + ', ' + action + ']);
setTimeout('document.location = "' + link.href + '"', 100)
}catch(err){}
}
</script>
or
<script type="text/javascript">
function recordOutboundLink(link, category, action) {
try {
var pageTracker=_gat._getTracker("UA-XXXXX-X");
pageTracker._trackEvent(category, action);
setTimeout('document.location = "' + link.href + '"', 100)
}catch(err){}
}
</script>
This more or less the same as the answer from Crayon Violet, but has a nicer method name and is the official solution recommended by Google.
As above, this is due to the page being unloaded prior to the Async call returning. If you want to implement a small delay to allow gaq to sync, I would suggest the following:
First add a link and add an extra class or data attribute:
My Link
Then add into your Javascript:
$("a[data-track-exit]").on('click', function(e) {
e.preventDefault();
var thatEl = $(this);
thatEl.unbind(e.type, arguments.callee);
_gaq.push( [ "_trackEvent", action, e.type, 'label', 1 ] );
setTimeout(function() {
thatEl.trigger(event);
}, 200);
});
I don't really condone this behavior (e.g. if you are going to another page on your site, try to capture the data on that page), but it is a decent stop-gap. This can be extrapolated not just for click events, but also form submits and anything else that would also cause a page unload. Hope this helps!
I had the same issue. Try this one, it works for me. Looks like that ga doesnt like numbers as a label value. So, convert it to string.
trackEvent: function(category, action, opt_label, opt_value){
if(typeof opt_label === 'undefined') opt_label = '';
if(typeof opt_value === 'undefined') opt_value = 1;
_gaq.push([
'_trackEvent',
String(category),
String(action),
String(opt_label),
opt_value
]);
}
I have a YUI button defined through HTML markup. I managed to get it loaded and "skinned" properly.
The problem is a custom click event. I have tried a number of approaches all of which links the custom function to the 'click' event, but no matter which way I do it, it ALWAYS triggers upon page loading and then it doesn't fire when clicked. I can't seem to get it to "wait" for a user to click. It just shoots like a virgin on his first date.
Code below....
<script type="text/javascript">
YAHOO.util.Event.onContentReady("submitbutton", onButtonReadySubmit);
YAHOO.util.Event.onContentReady("editbutton",onButtonReadyEdit);
var myTabs = new YAHOO.widget.TabView("demo");
function editDoc(sBaseRef, sUNID) {
var sNewURL = sBaseRef + "/0/" + sUNID + "?EditDocument";
alert("Going to : " + sNewURL);
window.location.href=sNewURL;
}
function onButtonReadySubmit() {
var oSubmitButton = new YAHOO.widget.Button("submitbutton");
}
function onButtonReadyEdit() {
var oEditButton = new YAHOO.widget.Button("editbutton");
YAHOO.util.Event.addListener("editbutton", 'click', editDoc('a URL path goes here' , 'A PageKey goes here'));
}
YUI Button publishes its own click event that you subscribe to on the YUI Button instance, rather than using YAHOO.util.Event.addListener. This is described in detail on the YUI Button landing page: http://developer.yahoo.com/yui/button/#handlingevents.
Your problem is the 3rd argument. That should be a reference to function. What happens in your code is that the 3 argument is a function that is called as the listener is being created, and returns nothing.
You have:
YAHOO.util.Event.addListener("editbutton", 'click', editDoc(
'a URL path goes here',
'A PageKey goes here'
));
Simply what you want is:
YAHOO.util.Event.addListener("editbutton", 'click', editDoc);
However, you also want to pass 'a URL path goes here' and 'A PageKey goes here' to the function at click-time. To do this, use the optional fourth argument to addListener() - an object to pass to the function.
function editDoc (ev, oArgs) {
var sBaseRef = oArgs.url,
sUNID = oArgs.key;
/* code here */
}
YAHOO.util.Event.addListener("editbutton", 'click', editDoc, {
url: 'a URL path goes here',
key: 'A PageKey goes here'
});
See http://developer.yahoo.com/yui/docs/YAHOO.util.Event.html#method_addListener for more.
As Todd mentions, you can also do this as part of the YUI Button creation - but the same issues of function versus function references apply.