For some reason, I see a lot of SPAs right now, some of them actually embedded into otherwise harmless and perfectly likable web sites. I guess they’re not going to go away anytime soon. This article is part of my “don’t fight it” exercise.
It is a follow-up to the article on tracking single-page applications. I want to add two important things:
- a brief discussion on EBRs and hashes, and
- a workaround for the fact that the
sobject is only instantiated once and we need to be careful with
s.t()calls and what data they send.
EBRs & Hashes
Back in the days, SPAs had this ugly habit of breaking the browser navigation (back button would exit the SPA rather than going back “one page”), until someone decided that the hash part of the URL could be used to simulate pages.
So these days, most SPAs do change the URL when they render a new “page”.
In an SPA that displays books, I could move from the overview (on
http://my.book.site/books.html#/overview) to one of the books (e.g.
http://my.book.site/books.html#/john+m+harrison/light), or maybe to a list of some books (on
This is great news for your friendly marketer, and for you, too!
What it means is that we no longer rely on you — the developer — to call DCRs at every turn. Instead, we can use EBRs along with the “pushState or hashchange” event type in DTM.
In the Analytics part of that rule, we select “s.t(); – increments a pageview”, and we specifiy a page name.
The “Page Name” Data Element can potentially be really easy, and just read
window.location.hash, but that would be cheating, wouldn’t it?!
That EBR completely covers normal tracking, which is why I usually call it “Quasi-normal Page Load”.
Everybody happy. Except…
The clearVars Conundrum
When we are dealing with an SPA, conceptually, there will be one single Page Load Rule, which runs when the SPA is loaded. That happens when someone lands on it for the first time, or when they reload. The normal flow within the SPA does not reload any pages, though.
As a result, we get a tracking object (“
s object”) that never dies. Or rather, unlike the usual “page scope”, where a loading page creates the
s object, and the next page creates a new one, in an SPA, the lifetime of the
s object is unnaturally long. It is immortal, so to speak.
Now that is an issue.
A long time back, I used to write on this blog about “variables”, and I always put that in quotes, because you really shouldn’t see something like
s.eVar47 as a variable if you’re a developer. I used to call it a container.
But then I spent a day with my kids in an amusement park last month, and now I have a better analogy:
We went on a roller coaster. There was a (nicely short) queue, which at the end split up into individual queues with little barriers. When a roller coaster train arrived, the barriers would let two people pass and sit on the train, then close again. The train would then head off.
s object is like those individual queues at the end with the little barriers. You put people (values) into each little queue (eVarXYZ, propXY, …) as needed, then the train arrives, takes them and leaves (
The big difference is that
s.t() copies the people, so they are still at the head of the queue once the train has left.
That means that the next train will grab the same people, plus those that have arrived at formerly empty little queues in the meantime.
Think about custom tracking on some visitor action. You likely want to track some event and maybe an eVar, but I’m pretty sure you’re not interested in all the other stuff that currently sits in the s object.
And that’s ok, that’s why we have
s.linkTrackEvents. Works as designed, I’d say.
But for an
s.t() call, which sends the lot, we usually except the s object to be shiny and new, which in an SPA, it isn’t. Nobody originally thought we would reuse the object, but that’s what SPAs do.
The answer is a little-known utility method, that is part of the AppMeasurement core code:
s.clearVars() method deletes the values for most of the “variables”, or it takes all those people standing by the mini-gates and tells them to leave. Nicely.
We can use it with an SPA like so:
- visitor moves to “new page”
- track the “new page”
Or like so, pre-emptively, so to speak:
- track “new page”
Whichever way, only by calling
s.clearVars() can we be sure our tracking doesn’t have “old values”.
2017-06-29 update: my colleague JB Creusat points out that contextData is not emptied, so you still need to do that manually!
The first time I implemented this, I had to do it the first way, i.e. call
s.clearVars() before tracking, which raised an interesting question: how can I make sure those two things happen in that exact order?
In my SPA, I had an EBR that listened to “pushstate or hashchange”, then tracked a bunch of “variables” and some events.
When I realised that all “variables” and events from the previous “page” were also on the tracking call, I did the following:
- created a new DCR
- moved all tracking from the “Analytics” section of the EBR over to the DCR (“moved” as in: created the same things in the DCR, then deleted them in the EBR)
- in that script, called
s.clearVars(), then the DCR via
As a result, the EBR (which is still being called) doesn’t actually track anything. But it cleans all the “variables”, and then calls a DCR which does. A bit like a manager who takes a request, then passes it on to a worker.
Easy peasy, isn’t it?