The question of how to use DTM on Single Page Apps (SPAs) is a VERY hot item right now. By Single Page App, I’m referring to a full user flow contained on a single web page, so as to provide the user a more seamless experience. Often, these pages act like typical web pages, but they don’t always change URLs or load new resources. Many common web development technologies, such as Angular.js, Ember.js, and AJAX use SPA principles.
Unfortunately, there isn’t a single great answer for how to deploy DTM- it depends on many things. I’ll work through some of the options and the limitations to be aware of.
Suppressing Page View Beacons
Whatever method you take for tracking page views in a SPA, keep in mind most SPAs do have one true “page view” when the DOM first loads. If you are going strictly with the DCR or EBR route, you may need to suppress the initial page view beacon the Analytics tool will want to set by default. Otherwise, in the example below where the developers are firing a direct call rule on all page views, you’d get TWO beacons on the first page and 1 on all subsequent pages.
Data Layer Considerations
You’ll need to make sure that whatever the sources of your Data Elements are (CSS selector, javascript objects, cookies…) have the correct values BEFORE your rule is triggered. I have an example site showing one way you might do this for a data layer (though you’ll need to look in the source code), but ultimately it’s going to depend on your site.
Variable Persistence
One last consideration is that once your Analytics object exists (as in, the “s” in “s.pageName”), variables set on it will continue to exist unless specifically overwritten. In most cases, you’d overwrite s.pageName with a new value so it isn’t a problem, but something like s.eVar5 may be set on the first beacon in your SPA, and not desired on subsequent beacons. You can use s.clearVars() to “refresh” your “s” object, but you have to make sure it fires at the right time- for example, after the beacon on Page A, but before DTM starts mapping Data Elements to variables for the beacon on Page B. How you do this will depend on the overall deployment method you choose.
Deployment Methods
1) Direct Call Rules
Perhaps the most straight-forward approach is to have developers fire a Direct Call Rule, like _satellite.track(“page view”) on every thing YOU consider a page view, whether it’s a fresh new DOM or not.
Advantages: | Disadvantages: |
---|---|
|
|
2) pushState or hashChange
Many SPA frameworks, like Angular, use a certain flag to let the browser know the user is viewing a new “page”.
DTM can listen for this flag in an Event Based Rule using a pushState or hashChange condition.
ADVANTAGES: | DISADVANTAGES: |
---|---|
|
|
3) Custom Event EBR
Another option, which feels a bit like a blend of the first two options, is to use a Custom Event-based Event Based Rule (and no, that’s not a typo- it’s an EBR based on the JavaScript Concept of a Custom Event). It’s possible Developers are already using this Custom Event concept for their own code and purposes, and DTM can just listen for it… or you can have developers set one specific to our DTM needs by using something like my digitalData.userAction hack.
ADVANTAGES: | DISADVANTAGES: |
---|---|
|
|
4) (NEW OPTION!) “dataelementchanged” Event Based Rule
Just in the last few weeks, a new option emerged as part of the AEM Context Hub integration. This allows DTM to listen for changes to certain data elements- for instance, you could have a rule fire whenever the “pageName” has changed. My friends at 33 sticks have a great blog post about this already for more info.
ADVANTAGES: | DISADVANTAGES: |
---|---|
|
|
For the issue of not being able to do Conditional logic in a DCR, what I’ve been doing to get around that is to have an intermediary DCR. I put the logic in the “base” DCR, which will fire other DCR’s based upon the result of the logic. Not ideal, but a workable solution.
Do you mean that you have a named event like “pageView” and then add your conditional logic inside of a script block, calling _satellite.track() when you want to run the specific rules? Feels like that defeats the purpose of having the DTM firing rule UI in the first place.
There are some smoother ways of doing it, but essentially, yes- one of the options is to do as you describe. In the end, you can either do something like _satellite.track(“page view”) on every page, then within the code of that “page view” Direct Call Rule in DTM, add some conditions based on the current page (for instance, add something extra on search results pages)… or have conditions on your page that decide whether to fire _satellite.track(“basic page view”) vs _satellite.track(“search results”), and have a cleaner DTM interface but more rules and code to manage on the front end. Neither option is awesome, to be honest, but I do see folks doing the former much more than the latter. I believe some of the options may improve when the next major iteration of DTM comes out (probably next Spring).
Never mind my last comment, I just noticed you mention that on a different tip page. 🙂
Hi Jennifer,
thanks for a great post. Really helpful. I had a question whether the clearVars function would also clear the evars/props that we want to have for the session or user level in Adobe Analytics. For example, we collect user id at one stage, send it to Adobe Analytics and then on the next page view we clearVars function to remove it. Would that mean that we cannot see user id in adobe analytics and use user id to see how many pages the user visited etc.? Thanks, Jonas
No, thankfully, clearVars doesn’t affect how the data is stored/persisted on the back end. If you are sending the data into an eVar and that eVar is set to not expire until, say, the end of the visit, then you could set it on page A, set clearVars, and Adobe will hold on to that value on page B. For eVars, clearVars generally doesn’t have much impact, because they’re set to persist anyways. It would just lower the “instances” metric because the eVar doesn’t get set over and over. But for props and events, clearVars will make it so your prop pageviews or your event metrics don’t get set again unless you explicitly re-set them.
Is it possible to suppress the initial page load rule for adobe analytics using DTM, and actually change the request into an event called app loaded?
I’m not sure I understand the question- you can suppress the initial page load rule by putting “return false” in your tool custom code (see http://www.digitaldatatactics.com/examples/DCRsuppress.html). But if you do away with that, you need to do something else to let DTM know to fire a rule. If you want to have it fire something for “app loaded”, you could either do a Direct Call Rule or an Event Based Rule- maybe something like http://www.digitaldatatactics.com/index.php/2016/04/20/setting-up-an-event-based-rule-that-be-fired-directly-like-a-direct-call-rule/?
Hi,
Unfortunately i don’t see clearVars being used in DTM as it is very hard to use it especially when you make use of direct calls and location change (event based rule).
Has anyone been successful implementing that function? I had to work on my own function to clear all variables based on the rule.
I would instead expect a more friendlier option from DTM, i.e. option in the UI to confirm that you would like to clear the vars.
Thanks
I believe the UI will eventually have this option; in the meantime, if you’re in an EBR, you can add this as a custom condition for the rule:
s.clearVars()
return true
This will run before the analytics tool portion of the rule, and guarantee you have a fresh slate for your variables. This is one reason I prefer EBRs over Direct Call Rules.
Does anyone ever tested mutationObserver ? It does not depend on the framework and could be used to detect change in the page you are interested in.
Hm, this is something I hadn’t heard of before, thanks for the tip. So this would be in place of using “dataElementChanged” (which relies on a data layer or CSS selector value, for instance), “element is present” (which doesn’t work that great, IMO), or pushstate/hash change?
Hi Jenn,
For a React based site we are using Direct call to trigger both event based & page level tracking(set to s.t() in DTM). Now we notice the variables set in the Direct call to track events are flowing into subsequent page load calls. Any suggestions to remove those additional variables?
Thanks,
Ah, the classic pesky clearVars problem. This is an issue in all SPA implementations- s.tl beacons are good about only picking up the variables you want (using s.linkTrackVars and s.linkTrackEvents) but s.t beacons will always pick up all the variables on the page. If you need to get rid of all the variables that were previously set, you can use the s.clearVars function. The problem when you are implementing your SPA through DTM is there isn’t a good place to run that s.clearVars function- you need it running after the previous beacon fires, but before you start setting variables for the current beacon.
As I hint at in my post, this is one reason I don’t like using Direct Call Rules- there is simply no place to put that function that won’t mess with your current beacon but will consistently run at the right time. There are some workarounds- http://www.digitaldatatactics.com/examples/DCR.html has some examples but I’ll admit I hate using those workarounds, they can cause performance issues. If you can switch to using Event Based Rules instead, then you can just run s.clearVars in the custom condition code block, which will always run before the analytics tool part of your rule.
In theory, Launch will have clearVars as an option built in to the interface (or something similar) to solve this problem. In the meantime, you may also be able to try the s.registerPostTrackCallback function that should run after your s.t/s.tl beacons. I’ll admit I haven’t done this myself, but @josh in #measure slack had posted this, which you could put in your s_code next to your doplugins:
s.registerPostTrackCallback(function(){
s.clearVars();
});
// do plugins
s.usePlugins=true;
s.doPlugins=function(s) {
// stuff
};
Hi Jenn
I have a problem , wherein I set an EBR to track click count on a link with Custom code like s.eVar6=”mylink”. Problem is this eVar6 is also gets passed in subsequent rules where this eVar6 is not even specified.
I am just wondering if s.clearVars() would do the trick for me, but just wondering where to place it.
Yes, clearVars would do the trick. You can fire it before the Analytics section of the EBR (so it doesn’t “clear” any of the variables you are TRYING to set) by putting it in the Custom Conditions code for that rule:

You will need your “s” object to be globally scoped for this to work.
Alternatively, you could use the registerPostTrackCallback function in your s_code to force variables to clear out AFTER every beacon-
s.registerPostTrackCallback(s.clearVars)
I am running into an issue with the s.clearVars() function. Once a data element has been defined it is retaining that value whenever we call the variable on another page.
For example, we have a data element called ‘adsServed’ mapped to prop20, which gives the name of any third party ads we are showing on a given page. These ads CAN show up on any page, but don’t have to (example: we could have an ad show on the search results page, but not on the property page). We want to set prop20 if it exists so we define it globally.
So if we show an ad on the search results page, we set a value ‘SRP_ad’ which works fine. Then we move to the property page, where we ask if there are any ads. Our data object doesn’t return a value (expected), but prop20 is still set as ‘SRP_ad’ on the property page. _satellite.getVar(‘adsServed’) still shows a value of SRP_ad even though the clearVars function has run as part of our direct call rule.
Is there any way to clear out the values of _satellite.getVar so that it always pulls in the current value of the analytics object we have created?
Alas, this is a really annoying problem (and one I’ve experienced too).
Data Elements with a “page view” persistence do, unfortunately, hang around longer than we want them for Single Page Apps. It doesn’t have to do with clearVars or even Analytics, it’s strictly a DTM/Data Element problem: once a data element is set on a given DOM, it can only be overwritten by a new value for that data element- for instance, if my DE “search term” pulls the value “red wug” from digitalData.searchTerm for one page, then on the following page digitalData.searchTerm doesn’t even exist for my DE to check, it will continue using the “red wug” value it got from the first page- it doesn’t think it should override a value (“red wug”) with nothing.
I’m impressed you figured out that it was the data element- usually people get stuck at the fact that you set clearVars (which clears out “s” objects) but in this case, it’s the data element that’s persisting, not the “s” variable
This issue actually caused a recent conversation with the Launch product team on #measure slack, which resulted in Data Elements in Launch having a a new persistence=”none” option (in addition to pageview, session and visitor.)
Unfortunately, if you’re in DTM, your options are still limited. I haven’t found a great solution, short of moving into custom code (where you can check if the source of the data element still exists and has a value), or making sure the data layer (or whatever your source for that data element) still exists on subsequent pages, just with no value- for example, if page 2 sets digitalData.searchTerm=”” rather than the searchTerm object simply not existing.
Or move to Launch.