Jeroen Jeroen - 16 days ago 10
Javascript Question

How to send a ga command to a tracker with no name?

We have a custom Google Analytics plugin that will be sending commands to track custom pageviews and log events. Here's a simplified repro of our scenario:

// Version 1: only works with analytics.js, not with GTM

function GaPlugin(tracker, config) {
// Simplified for SO, we actually use the config argument
// to track more interesting pages and events...
ga(tracker.get('name') + ".send", "pageview", "/virtual/page/view");
}

var ga = window[window["GoogleAnalyticsObject"] || "ga"];

if (typeof ga == "function") {
ga("provide", "myAnalytics", GaPlugin);
ga("require", "myAnalytics", { /* omitted for simplicity */ });
}


This works fine if
analytics.js
is included directly on the page. However, we're now including universal analytics on the page using the Google Tag Manager. Thus we ran into the dreaded error...


Command ignored. Unknown target: undefined


...as seen in the Google Analytics debugging Chrome plugin when the
require
command is being executed.

This question and this blogpost imply the need to set a tracker name (in GTM, using: Edit Tag => More Settings => Advanced Configuration => Set Tracker Name checkbox) but the tooltip says "Use of named trackers is highly discouraged in GTM". So instead I changed things to this:

// Version 2: crashes with GTM because ga is loaded after this code

function GaPlugin(tracker, config) {
// Simplified for SO, we actually use the config argument
// to track more interesting pages and events...
ga(tracker.get('name') + ".send", "pageview", "/virtual/page/view");
}

var ga = window[window["GoogleAnalyticsObject"] || "ga"];

if (typeof ga == "function") {
ga("provide", "myAnalytics", GaPlugin);
ga(ga.getAll()[0].get("name") + ".require", "myAnalytics", { });
}


But now I'm met with an error because
ga
is
undefined
when the above executes, because GTM will load universal analytics asynchronously, where I want to run my code to bootstrap the plugin right away. This also means I cannot place a callback on the
ga
command queue, because again: it doesn't exist yet.

In essence, the order of things (I think) is now:


  1. The GTM snippet starts loading (async).

  2. My own javascript code runs.

  3. GTM will start loading analytics (async).

  4. Analytics is loaded and ready to be used.



The only workaround I could think of was this:

// Version 3: ugly workaround...

function GaPlugin(tracker, config) {
// Simplified for SO, we actually use the config argument
// to track more interesting pages and events...
ga(tracker.get('name') + ".send", "pageview", "/virtual/page/view");
}

var interval = setInterval(function() {
var ga = window[window["GoogleAnalyticsObject"] || "ga"];

if (typeof ga == "function" && typeof ga.getAll == "function") {
ga("provide", "myAnalytics", GaPlugin);
ga(ga.getAll()[0].get("name") + ".require", "myAnalytics", { });
clearInterval(interval);
}
}, 250);


Isn't there a better way to do this? The GTM documentation nor the GA Plugins documentation seems to have any info on this.




As a footnote, I just realized it might also work if I mimic the tracking snippet by creating
ga
as a command queue myself. But that also feels like a workaround, not a solution...

Answer

Not satisfying, but let me share my workaround as an answer nonetheless, as it is in fact the solution I'm using right now. It builds on the final footnote: mimicking what the Google Analytics bootstrapping snippet does.

I'm setting up the ga object, or at least its q property, myself:

function createGoogleAnalyticsQueueIfNeeded() {
    // As a workaround for: http://stackoverflow.com/questions/40587544
    // We mimick: https://developers.google.com/analytics/devguides/collection/analyticsjs/tracking-snippet-reference
    var gaKey = window["GoogleAnalyticsObject"] || "ga";
    var ga = window[gaKey] || function () {
        (window[gaKey]["q"] = window[gaKey]["q"] || []).push(arguments);
    };
    window[gaKey] = ga;
    return ga;
}

After calling the above, you can place a command on that queue which will be executed whenever GTM has finished loading GA (which also includes an optimization to run the plugin for all trackers):

var ga = createGoogleAnalyticsQueueIfNeeded();

ga(function() {
    ga("provide", "myAnalytics", GaPlugin);
    ga.getAll().forEach(function(t) {
        ga(t.get("name") + ".require", "myAnalytics", { });
    });
});

Et voila, the provide and .require calls now run whenever GA (loaded via GTM) hollers at the callback.

Comments