How do I use GTM with Angular?
I'm trying to fire a (virtual) pageview event when I load a new partial using this code:
dataLayer.push({
'event' : 'pageview',
'pageview' : $location.path(),
'virtualUrl' : $location.path()
});
But I don't see the event firing (I'm using the Google Analytics Chrome debug extension to view fired events).
I find the Chrome extension unreliable.
Simply run the global variable dataLayer in the console to print the array of events. One of the objects should be your pageview event.
Here is an example of how we are using it:
Note: we're not simply using $location.path(), instead everything in the url after the domain. Which includes the .search() & .hash().
$location in the Angular docs
modules/analytic.js
(function(window, angular) {
'use strict';
angular.module('Analytic.module', ['Analytic.services']).
run(function($rootScope, $window, $location, GoogleTagManager) {
$rootScope.$on('$viewContentLoaded', function() {
var path= $location.path(),
absUrl = $location.absUrl(),
virtualUrl = absUrl.substring(absUrl.indexOf(path));
GoogleTagManager.push({ event: 'virtualPageView', virtualUrl: virtualUrl });
});
});
})(window, window.angular);
services/analytic.js
(function() {
angular.module('Analytic.services', []).
service('GoogleTagManager', function($window) {
this.push = function(data) {
try {
$window.dataLayer.push(data);
} catch (e) {}
};
});
})();
In GTM
You'll need {{virtualUrl}} and {{event}} Macros which listen for the dataLayer variables of the same name.
You'll need a Google Analytics Event Tracking Tag with a Firing Rule which triggers when {{event}} equals 'virtualPageView'. Make sure you remove the default 'All Pages' Rule which makes it run on every page load. Instead, you want it to run when you dataLayer.push() the event, which may happen multiple times per page refresh.
The Tag should be configured with:
Track Type == 'Page View'
More Settings > Basic Configuration > Virtual Page Path == '{{virtualUrl}}'
Similar to the accepted answer, we created a simpler, more explicit solution using a pagename variable in a Javascript in our controller for our single-page app,
// Note, this may not be how your app works, YMMV
var pagename = $location.path().substr(1,$location.path().length);
and push them to the dataLayer like this:
window.dataLayer.push({'event':pagename+'-page'})
Then in GTM we added triggers in GTM like so:
Trigger: Custom Event
Event Name: home-page
... about-page, faq-page, etc.
For more complex angular apps, there are some available extensions for using Google Analytics and Google Tag Manager with Angular.
See Angulartics (which also supports web analytics solutions other than Google Analytics via its plugin architecture) and the related NPM package for GTM.
I would highly recommend you to use the angulartics library. I have used it on multiple sites. It gets you running pretty quickly.
It includes support for Virtual Page Views and events out of the box. It does not support GA eCommerce but has support for extensibility.
Also - I have used with both GTM and Piwik.
No need to add code.
Configure a "history change" trigger, this is triggered by angular route changes, add it as a trigger to your "page views" tag.
The possible way to do this is using $window service.
Look at this answer to try, if it works: Tracking Google Analytics Page Views with Angular.js
Hope it works.
Related
Description
Since old GA SDK is getting obsolete, we are migrating to GTAG.
While migrating, I found this line :
ga('set', 'checkProtocolTask', null);
which disables the protocol check so that GA script works in a webview (where cordova uses the file:// protocol apparently)
What I've tried
I looked through the internet (about 2-3 pages of "similar but not quite the same" problems)
Only solution I found is there : https://support.google.com/analytics/answer/7475953?hl=uk
TLDR : add a script in Google Tag Manager, which will disable the protocol check for all trackers in google analytics and use that script in Google Analytics setup.
But :
* we are not using Google Tag Manager as of now, so we'd have to set the thing up just for that.
* it seems like a dirty hack to me... But it's not better than the old version I guess...
* I don't think it's good to have this piece of code in Tag Manager, when everything else is in GIT repo.
* It means we are disabling the protocol check, wherever it is called. Whereas the "hack" we had only disabled the protocol check when building the mobile app (desktop web was working without this line)
Is there no other solutions ? am I wrong ?
So... I just realised that using global site tag just uses ga as we used to.
So I actually ended up using the solution from the page I found : https://support.google.com/analytics/answer/7475953?hl=uk
Only I used that code snippet in my page instead of in a GTM variable.
So whenever I load my page, I do
ga(function() {
var trackers = $window.ga.getAll();
trackers.forEach(function(tracker) {
tracker.set('checkProtocolTask', null);
});
});
I wrapped the snippet in a function() {...}, that way it is apparently run after ga is initialized.
For Google Analytics 4 this feature is not yet implemented. So you cant disable protocol check. see the status here https://issuetracker.google.com/issues/174954288?pli=1
My question is for someone with experience using Google Analytics Linker Plugin programmatically. However my example has a bit complicated setup.
I'm currently working on the website which is using Google Tag Manager for loading GA scripts. It loads several GA scripts on the same page for different purposes.
This website also has a custom dropdown with related domains and I have to use GA Linker Plugin in order to keep them connected. I have to do it manually through the code on every domain element click event. I used the setup suggested by Google Analytics docs:
// inside onclick handler
ga(function(tracker) {
var linkerParam = tracker.get('linkerParam');
// apply to url and navigate window.location.href = url etc.
});
Obviously this doesn't work in my case because of multiple trackers on the page:
// inside onclick handler
ga(function(tracker) {
// tracker is undefined :(
});
I managed to check how many trackers are available and requested linkerParam on each:
// inside onclick handler
ga(function () {
var trackers = ga.getAll();
trackers.forEach(function (tracker) {
console.log(tracker.get('name'), tracker.get('trackingId'), tracker.get('linkerParam'));
});
});
// outputs
// gtm1 UA-XXXYYY-1 _ga=2.234343242.904959305.3434234324-394093204.3094039402
// gtm2 UA-XXXYYY-2 _ga=2.234343242.904959305.3434234324-394093204.3094039402
// gtm3 UA-XXXYYY-3 _ga=2.234343242.904959305.3434234324-394093204.3094039402
As you can see all trackers have the same linker param value, but different names and tracking ids. My question are -
Is it safe to use just first tracker from the list as long as all values are the same (e.g. ga.getAll()[0].get('linkerParam'))?
Or will it be safer to create a specific name for one of the GA trackers in GTM and get it by name in code, e.g:
// inside onclick handler
ga(function () {
var tracker = ga.getByName('websiteTracker');
console.log(tracker.get('name'), tracker.get('trackingId'), tracker.get('linkerParam'));
});
// outputs
// gtm3 UA-XXXYYY-3 _ga=2.234343242.904959305.3434234324-394093204.3094039402
Thanks!
You might be overthinking the problem. Google Analytics through GTM has a simple, built-in way to implement cross-domain tracking. For each GA property that you are loading through GTM, simply set the domains you want to link in the "Cross-Domain Tracking" fields of the Analytics Setting variable or in the over-riding settings in the GA tag.
Bounteous has a very detailed article on how to implement and debug this here.
This has worked in almost all cases in which I want to implement cross-domain tracking through GTM - even if it is for numerous domains.
In the case that you actually need to do this programmatically, I'm pretty sure you can use the same linker param for all the GA properties. You can verify and debug your implementation by doing something like:
Open up the real-time report in the GA property you want to test cross-domain tracking
Visit domain1.com with these UTM values appended: domain1.com?utm_source=test&utm_medium=test
You should be able to filter the real-time traffic by source / medium by clicking on "test" as source or medium under the traffic sources tab.
Navigate to the content tab of the real-time report, you should see the page path and page title for domain1.com
For each domain you want to test that cross-domain tracking works, click the link in your navigation
If everything works, your filtered real-time view should update to the page path and title of domain2.com
If cross-domain linking isn't working, the filtered real-time report will not update. Removing the filter, you should see "domain1.com / referral" or "(direct) / (none)" as the source / medium depending on your referral exclusions.
Hopefully this will help you configure cross-domain tracking or debug efficiently.
I am currently working on a mobile app using version 1 of the Ionic framework. In this app I am using Google Maps to display a map. The map is being loaded using a script tag and works well while the device is connected to the internet. But if the app is started without an internet connection, the app won't even open. How can I solve this problem?
I am not sure why an Ionic app refuses to open at all when a library cannot be loaded, but luckily this issue can be solved.
TL;DR: You have to load Google Maps asynchronously, so check whether there is a network connection and if there is load the library and otherwise show an error.
There are many examples of how to load the Google Maps library asynchronously, like this post from Google, or this question which contains an answer to your question or this question in which the differences between loading using the script tag and loading asynchronously are explained. There is also this question in which it is shown using plain JavaScript.
Now of course that is a lot of text to go through, so here is the summary. Instead of adding a script tag in index.html, you have to load the Google Maps library only when there is an internet connection and load the library using JavaScript. In order to check for an active internet connection, you are going to need cordova-plugin-network-information, which you can find here. You can also use ngCordova to make it easier to use this plugin, you can find the documentation here.
In your controller, you can do something like this:
.controller('MapCtrl', function($state, $scope, $window, $ionicPlatform) {
// This view event is fired only when the view is loaded for the first time.
$scope.$on("$ionicView.loaded", function(scopes, states) {
var isMapLoaded = false;
var networkState = navigator.connection.type;
var isOnline = (networkState !== Connection.UNKNOWN && networkState !== Connection.NONE);
if(isOnline) {
// If there is a network connection, we load the map.
var mapsURL = 'https://maps.googleapis.com/maps/api/js?key=[INSERT API KEY HERE]&callback=';
$window.mapLoaded = function() {
isMapLoaded = true;
console.log("The map has been loaded.");
}
function loadMapsLibrary(url, callbackFunctionString) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url + callbackFunctionString;
document.body.appendChild(script);
}
$scope.isMapLoaded = isMapLoaded;
loadMapsLibrary(mapsURL, "mapLoaded");
} else {
// It's up to you what you do here, you can show an error for example.
}
In your view, you may have to use the ng-if directive and the isMapLoaded value to reload the <div> containing the map to ensure that it loads. This should give you a basic idea of what you need to do to get your app working again.
I've a HTML page which has some DOM configured with Angular. Now I'm building a chrome extension to modifying value in a text box. element.value= newValue will not work as the text box is designed with Angular. After reading some resources, I came to know that I need to do the changes to the scope of the element. I tried doing this in the extension.
var eleScope = window.angular.element(document.querySelector('.textbox')).scope();
eleScope.$apply(function() {
eleScope.item.request.box = newValue;
});
This doesn't seem to work as intended. When I dug into the problem more deeply, I came to know that the angular in the scope of window was not getting the right scope.
I also tried to inject angular.js from my extension and using angular.element directly which also doesn't seem to solve the problem.
Am I missing something. Appreciate your replies.
Hmm.. Actually you should be just able to change the value using vanilla Javascript or JQuery and should not need to update the scope as it is two-way binding ie. Updating the view updates the model and vice-versa.
The only reason why the model does not update is because the watchers to update the model trigger only on particular events like "click" or "input".
So the solution here would be to manually trigger these events.
This can be do by:
// the selector used is based on your example.
var elementTOUpdate = $('.textbox');
input.val('new-value');
input.trigger('input');
The last statement will trigger all the handlers on the element for the "input" event ( which will fire the angular watchers ) and update the scope.
More info on the trigger function:
http://api.jquery.com/trigger/
Although your web page and your Chrome extension's content script share the same DOM, they have separate JS execution contexts. In other words, despite grabbing the element via angular.element().scope() in your Chrome extension, you're still not accessing the correct scope object. (See the Chrome dev docs for more on this.)
In order to get the modified data from your Chrome extension on to the page, you can either manipulate the DOM via your content script, or take some extra steps to get the modified data into the web page's execution context (and subsequently, into your controller's scope).
For the latter, the solution is to use the Chrome's Message Passing API; specifically, the API designed to communicate between the web page and the Chrome extension (see here).
Here's an example from the documentation:
First, enable the API for the appropriate website(s) in your manifest.json file:
"externally_connectable": {
"matches": ["http://*.foo.com/*"]
}
From the web page, make a request to connect to the Chrome extension. In your callback, you'd update your model:
// Chrome extension's ID
var extensionId = "abcdefghijklmnoabcdefhijklmnoabc";
chrome.runtime.sendMessage(extensionId, {foo: 'bar'},
function(response) {
if (response.success) {
$scope.myData.foo = response.data;
$scope.$apply();
}
});
In your Chrome extension's background.js file, add a listener for the request:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (request.foo) {
sendResponse({ data: 'modified data goes here' });
}
});
This process will allow your Chrome extension to send data into the correct execution environment, allowing your AngularJS app to work with it.
The main problem is well explained in Jonathan Guerrera's answer: you can't just load Angular in your extension and expect it to work because it's a different instance of Angular; the real one is in another JS context.
Another possible (though hacky) solution is to inject code into the page's context. That way, you're actually manipulating the page's own Angular.
It can be combined with other methods of communicating between the page-level script and the content script.
Currently in my website, I used HTML5's pushState() and popState in links to increase the speed. However, this doesn't really change the real URL and it looks like it will affect and mess up the Google Analytics's code. (doesn't show a url change) Is there a possible solution for this? Thanks,
If you are using the newer analytics.js API, Google's documentation requires the following code to trigger the event:
ga('send', 'pageview', '/some-page');
If you are using the older ga.js API, David Walsh suggests AJAX websites to use the _gaq.push method:
_gaq.push(['_trackPageview', '/some-page']);
I know it's old question but since this question is first result in Google about tracking pushState() in Google Analytics and all answers are wrong I decided to answer it.
In other answers they mentioned to use directly ga('send' ..... ) but this is wrong way to do it.
First you have to 'set' parameters and then use 'send' to track it.
If you want to update only url, use following code
// set new url
ga('set', 'page', '/new-page');
// send it for tracking
ga('send', 'pageview');
If you want to update url and title, add title parameter to it
// set new url and title
ga('set', {
page: '/new-page',
title: 'New Page'
});
// send it for tracking
ga('send', 'pageview');
Source Single Page Application Tracking - Web Tracking (analytics.js)
February 2018 Update - Global Site Tag (gtag.js)
Google Analytics has a new tracking code snippet, so the other answers might not work for gtag.
This is the default tracking code. It only runs once even though we try to run it each URL changes.
gtag('config', 'GA_TRACKING_ID');
But with a page_path parameter we can make GA run manually.
gtag('config', 'GA_TRACKING_ID', {'page_path': '/new-page.html'});
And we can make something like this.
var origin = window.location.protocol + '//' + window.location.host;
var pathname = window.location.href.substr(origin.length);
gtag('config', 'GA_TRACKING_ID', {'page_path': pathname});
Single page application tracking with gtag.js (Google documentation)
Recent answer (2017)
You can now use Google's autotrack.js, a script that enhances analytics.js.
It includes a plugin that automatically tracks URL changes for single page applications.
You just need to include the script and the following line in your html:
ga('require', 'urlChangeTracker');
2020 Update
If you are using Google Analytics 4 you don't need to push the event anymore IF you enabled the Page views option in the Enhanced measurement feature in Data Streams menu.
At the time of writing, here in September 2013,
Google Analytics has a new JavaScript API.
After you've included Google's new "analytics.js" asynchronous snippet, use the send pageview command to track pages:
ga('send','pageview');
After your pushState madness, use this send pageview command to track your asynchronous navigation. According to Google's Documentation on Page Tracking with Analytics.js, the send pageview command will magically read and track the new location given by pushState, as it will, in the moment the send pageview command is called, use the following values by default (though you can specify them):
var locationToTrack = window.location.protocol+'//'
+window.location.hostname
+window.location.pathname
+window.location.search;
var titleToTrack = document.title;
var pathToTrack = location.pathname+location.search;
Note, Google's standard analytics snippet includes an initial send pageview command.
Update:
I've implemented Google Analytics tracking on my website in the way above that I described -- however, it does not seem to be successfully tracking any of the pushState page views.
I'm going to try specifying the location, title, and page name explicitly on the send pageview command, and see if GA tracks properly.
I'll post back with the results, unless I forget.
Using ga('send', 'pageview') was not registering a pageview on GA.
The way that worked for me is:
window.ga.getAll()[0].set('page', location);
window.ga.getAll()[0].send('pageview');
I can use it after changing the url with history.pushState.
I also had problems with ga('send','pageview');, after using history.pushState.
The workaround was simply make the URL explicit.
ga('send','pageview','/my-url')