Tracking Single-page Applications

or: why AngularJS makes our lives harder

Single-page applications are web sites that “live on a single page”, i.e. when the visitor clicks anything, the browser will not load a new page (as it usually would). Instead, it will modify the existing page using Javascript.

SPAs can be quite complex, and people build them because they feel more like an app. If well done, they can also be really fast, because there is no network delay after a click.

Frameworks like AngularJS and ReactJS are pretty popular.

Except with us, the Analytics people.

We distrust them, and SPAs in general. They don’t work the way a web site should work.

Written down like this, our general attitude towards SPAs sound a bit like we’re just afraid of them because we don’t know them. That might be true.

Truth be told, SPAs are a lot like mobile apps, in the sense that we should just treat them like a piece of software, into which we plug our SDK plus some code that handles tracking.

SPAs are Apps

Thinking about it like that makes it easier for us (including your friendly marketer) to understand what we have to do to get data out of an SPA.

There are some things we must do.

First of all, we need to work more closely with the development team (you), because we cannot entirely rely on tag management, or maybe not at all.

The two big aspects of that are freedom and release cycles.

For an SPA, we have a lot less freedom than with a classical web site.

There will be a lot more things that we need from you, such as explicit calls to the _satellite.track() method and Direct-call Rules.

This goes against our wish for control, obviously, so don’t be surprised when your friendly marketer and your likable analyst act slightly more stressed than usual.

Secondly, we need to plan properly, document requirements, and live with the status quo longer than we’d like to.

Where on a classic web site with tag management, we have a lot of leeway, SPAs put our wishes into your hands.

If we want something tracked, we have to think hard about it up-front, let you know what we need exactly, work with you to make sure it works properly before deployment, then live with what goes live seemingly forever. All things we hate to do.

So… ?

Yes, sorry.

The best way to track SPAs, in my humble opinion, is to use Direct-call Rules, pretty much exclusively.

You’d build a single Page Load Rule for when the SPA first loads, which would ideally track things like the tracking code, or the version of the SPA.

For all further interaction in the SPA, I suggest you use DCRs, triggered straight from the controller.

Make sure you sit down with your friendly marketer and define all of the DCRs that you will need. Things like “newsletter sign-ups”, “form submits”, or “article views” come to mind, but it really depends on what your SPA does.

The alternative is to use Event-based Rules. EBRs can listen to a lot of different things, some of which are relevant in this context:

  • custom events
  • DOM objects appearing
  • DOM objects becoming visible

While EBRs hand a part of the freedom back to analytics and marketing, I wouldn’t use them anymore. The flexibility gained is offset by the overall flimsiness of the setup. Not worth it, if you ask me. We do have data quality issues even without things like this.

I’m not the only one thinking DCRs are the best way. See Craig Scribner’s article the case for DCRs.


Another question: how do you handle the Data Layer?

You cannot pass any parameters into a DCR when you call it. But sometimes parameters would be great! Think of calling a DCR for “article views”. You’d likely want to track the article ID or article name along with the fact that it was viewed…

There are a couple of ways you can pass information and use it in the DCR:

  1. Attach the parameters to window
  2. Attach them to _satellite
  3. Store them into a “CustomVar” in DTM

Example for all three of them:

// save parameters in window
window.myParams = {'articleID': '123'; 'articleName': 'Tracking SPAs'};

// save parameters in _satellite
_satellite.myParams = {'articleID': '123'; 'articleName': 'Tracking SPAs'};

// use CustomVars
var params = {'articleID': '123'; 'articleName': 'Tracking SPAs'};
_satellite.setVar("avParams", JSON.stringify(params));

The first two are very similar. My colleague Pedro Monjo introduced my to the second, and I think it is pretty cool, simply because a) it contains our stuff, and b) there’s less risk of a collision (someone else using window to store data under the same name).

The third way let’s DTM handle the storing.

You get that data back inside your DCR as follows:

// getting it back from window
var data = window.myParams;
var articleID = data.articleID;
var articleName = data.articleName;

// getting it back from _satellite
var data = _satellite.myParams;
var articleID = data.articleID;
var articleName = data.articleName;

// getting it back from CustomVar
var data = JSON.parse(_satellite.getVar('avParams'));
var articleID = data.articleID;
var articleName = data.articleName;

Use these in your Data Elements, then have your friendly marketer use those DEs to set props and eVars.

What I like about this setup?

It forces you and your friendly marketer to explicitly define what data should be surfaced under exactly what circumstances.

In my opinion, that is a good thing, if not a great thing. It is what testing the site infrastructure is about, too…

Also, your friendly marketer still has a certain flexibility. She can decide what exactly she wants to track or not. And she can modify the DCR to do all sorts of things, e.g. call more DCRs in specific situations.

(A slightly convoluted example would be: say she’d want to take a look at users who consume more than 5 articles a day. She could, within the ‘article_views’ DCR, set a cookie with a lifetime until midnight, into which she’d add one view every time the rule is called. Then, when the count hits 5, she’d set one more event and maybe a prop or an eVar with the count. No input needed from you.)

13 thoughts on “Tracking Single-page Applications

  1. Great post on a hot topic. Thanks Jan.
    I’m curious about your take on DCRs versus EBRs. My main complaint with DCRs is the inability to run additional conditions, and the lack of a place to put s.clearVars. Aside from that, I’m under the impression that DTM is moving more and more away from DCRs. Whats your take on the newest release, with the EBR condition that can listen for Data Element changes?


    1. Hi Jennifer,

      I tried using EBRs on a site with a delayed data layer, but ultimately, that failed. DCRs have this one huge advantage: things have to be defined between dev and analytics, which makes a lot of sense to me. I do not see that as a drawback.

      Re the new triggers: when I wrote the article, they weren’t available 😉


      1. I echo with Jenn Kunz , clearing variable is a big pain with DCR.

        EBR condition that can listen for Data Element changes is bit risky, we will run into timing issues, because all browser behavior is not consistent.

        So far I am using below method to dynamically capture data and DCR , so far good.
        // Create a window object for post-load communication.
        window.publishPostPageData = function (obj) {
        for (var attrname in obj) {
        if (obj.hasOwnProperty(attrname)) {
        pageDataLayer[attrname] = obj[attrname];

        Liked by 1 person

  2. I would add an additional option to passing parameters to DCR: use the data layer. The AJAX handler, after receiving the data from the server, updates the data layer and calls the DCR. The DCR can then just user the regular Data Elements.

    A completely different approach that I have used a few times is a message bus. I hope to have time some day to explain how I’ve used it.

    Liked by 2 people

  3. This week little fine tuned previous method call code, added publishPostPageData @ scode library like a plug-in code; from application side just a publishPostPageData method call with with 2 arguments , first one is a “key” for DCR , second argument is updated datalayer object for that particular DCR scenario. Hence no need of _satellite call from application side 🙂

    window.publishPostPageData = function(name, obj) {
        for (var attrname in obj) {
            if (obj.hasOwnProperty(attrname)) {
                pageDataLayer[attrname] = obj[attrname];

    Liked by 1 person

  4. Hi Jan,

    I guess the second way of storing it into _satellite. should be avoided, because you cannot make sure that the function will not be used by a future release of Adobe DTM. For example, if you choose to store the name of a building in _satellite.propertyName, it could be possible that Adobe will use it for the DTM Property Name in a 2017 release and it breaks your implementation without you making changes in it.
    I would agree if Adobe provided a sub element, which you can use for it, e.g. _satellite.customVars, and they will commit to never use it in a future release. In this case, you could use _satellite.customVars.propertyName and will be safe.

    However, the other two ways are perfect. Thanks for sharing!


    Liked by 1 person

  5. Hi Jan,

    I need to track a website which is built as a single-page app. First tracking call is standard and all following tracking calls are DCRs. The typical scenario might look as follows:

    1) First tracking call is captured in a standard way – pageBottom()
    2) Then, I navigate to other page which is tracked as a virtual pageview -> DCR – s.t()
    3) Then, some on-page action is tracked using DCR as –, s.eVar1=Some Action data
    4) Then, some other on-page action happens -> another DCR –, s.eVar2=Some Action data — for this case I need to clear eVar1 value from previous call
    5) Then, I navigate to different page -> again DCR – s.t() – now I want to clear custom events and also eVar2 value

    I’d like to avoid explicitly resetting each variable in every single DCR. Should I completely avoid using DCRs? / Is there any best-practice for clearing vars and using DCRs for single-page app tracking?

    Thank you:)


    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.