We have a single page Angular app with the Facebook pixel that is injected via GTM. In the first injection, we pass the following:
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '<OUR ID>'); // Insert your pixel ID here.
</script>
Note, there is no pageview event. We then push a custom "virtual pageview" event to the GTM datalayer that leads to a tag that sends the pageview to Google Analytics (working fine), and triggers an injection of the following code as well:
<script>
fbq('track', 'PageView', {
dim1:{{dim 1 macro}},
dim2:{{dim 2 macro}},
dim3:{{dim 2 macro}}
});
</script>
On EACH pageview, the above fbq is appended to the DOM with the proper dimensions populated (by viewing the source of the page).
On the FIRST pageview HIT to Facebook, the dimensions are present in the call (by viewing the network tab), but on the SUBSEQUENT pageviews, a hit is sent to Facebook, BUT no dimensions are sent, even though they are present in the code appended to the DOM.
For the most part, these dimensions do not change hit to hit, but could change based on the actions the user does throughout their sessions.
Any thoughts as to why the subsequent hits DO NOT include the custom dimensions?
You try to update a payload every time a history change happens in an Angular app and execute the fb code again with the updated payload?
I would implement a datalayer on your front-end:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'virtualPageView',
pagePath: '/newPath', //update this path from your angular route,
//or use GTMs built in history-change trigger
//and a variable that gets the URL fragment
//to update your pageview's path variable.
customDimenson1: 'value1',
customDimenson2: 'value2',
customDimenson3: 'value3'
});
</script>
Create a trigger of the type Custom Event with the name virtualPageView.
Create 4 variables of the type Data Layer Variable, for pagePath, customDimension1-3.
Create your Custom HTML Tag which contains your fbq event:
<script>
fbq('track', 'PageView', {
dim1:{{customDimenson1}},
dim2:{{customDimenson2}},
dim3:{{customDimenson3}}
});
</script>
Create another Custom HTML Tag that fires on the built-in trigger All Pages, as it only needs to be executed once for your Single Page Application, with your base code:
<script>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '<OUR ID>'); // Insert your pixel ID here.
</script>
I had problems with SPA for updated variables. Sometimes it's a race condition, sometimes it's bugs in GTM or your code. You can also try to delay the execution by using a 100ms timer (bad advice).
Received feedback from the official Facebook team, it seems that this is by design. On any subsequent call with a Pageview event, no custom data will be sent in the payload.
Related
I am using angular-meteor with pascalprecht.translate
the translations object have to be stored in the database in order that some users can modify the translations via a back-office
I subscribe the collection containing the translations and loads them in the .config()
it works well BUT when loading the application the page is rendered before the subscription is ready.an during a short period of time the texts are not translated.
I have to do something in order that we always see translations even during the begining
My question is the following:
Is it possible to make angular wait for the subscription to be ready before it is launched ?
Best regards
bboisvert
angular.module('test').config(['$translateProvider', function ($translateProvider) {
Meteor.subscribe('settings', function(){
test = Settings.findOne({name: "translations"});
$translateProvider.translations('fr-FR', test['fr-FR']);
$translateProvider.translations('en-EN', test['en-EN']);
});
}]);
In the Meteor.subscribe callback, you could do $rootScope.$broadcast('meteorSubscribed')
on your root-level directive/controller listen for it and set a $scope variable to say it is loaded. You could then use that variable for ng-if or ng-show
example:
$scope.$on('meteorSubscribed', function(){
$scope.meteorReady = true;
});
in template:
<div ng-app="test" ng-controller="RootController" ng-show="meteorReady">
<!-- your app here -->
</div>
You could also circumvent the whole event + controller setup and just set $rootScope.meteorReady = true; right in the Meteor.subscribe callback. Really depends on the setup of your app.
I have a HTML page which contains five child html pages.
Using angularjs $rootScope.$broadcast and $rootScope.$on I am sending common data back and forth between pages.
The problem is if get/post method fires by any of pages, it renders all the slibbing pages and thus call every ng-event
written in every html page, thus creates performance issue. How to avoid this rendering for every page.
you can specify your events by page, model, etc. eg,
$rootScope.$emit('sampleEvent', {
page: 'pageA',
data: 'your data if there is any'
});
$rootScope.$on('sampleEvent', function(event, data) {
if(data.page === 'pageA') {
// do something here
}
});
also if you are going to use rootscope, just use emit. Since it's rootscope it has the same affect as broadcast but it provides much better performance.
I'm currently trying to implement the standard Stripe Payments checkout dialogue. When I drop in the short <script> include described in the docs (https://stripe.com/docs/checkout) the button that should display is not being rendered.
When I put it in my top-level index.html file the button DOES display. When I put it into a partial that gets displayed when hitting a particular route it does not. I assume this is because it is not executing the Javascript because it's not happening at page load when it's in a route.
Is there anything I can do to get this to work in a route or should I just implement a custom form that points to the stripe.js library? Thanks.
The issue is that the js does not fire, as you suggested. A solution is to simply include the Stripe checkout.js in your index.html file, and then trigger the Stripe popup to open with your controller (or elsewhere).
In your index.html (or equivalent)
<script src="https://checkout.stripe.com/checkout.js"></script>
<!-- Angular script(s) -->
In your controller (or elsewhere)
var handler = StripeCheckout.configure({
key: 'pk_test_6pRNASCoBOKtIshFeQd4XMUh',
image: '/img/documentation/checkout/marketplace.png',
locale: 'auto',
token: function(token) {
// Use the token to create the charge with a server-side script.
// You can access the token ID with `token.id`
}
});
handler.open({
name: 'Stripe.com',
description: '2 widgets',
amount: 2000
});
// handler.close();
This is an adaptation per the Stripe docs at: https://stripe.com/docs/checkout#integration-custom
I am building a SPA using Angular.js. We use Google Tag Manager to load in most of our analytics/marketing scripts, which includes Google Analytics. I am also using ui-router to manage states/views.
I would like to send pageview events off to Google Analytics whenever a user browses to a different state in my app. Part of the complexity in doing this with GTM is that GTM creates a named tracker. That means that all GA events need be prepended with the tracker name. That would usually look like this:
ga('trackerName.send', 'pageview', {page: '/a/path/', title: 'A Title'});
GTM uses a randomly generated tracker name, so the tracker name needs to be grabbed at runtime. That can be done fairly simply with GA's getAll function. If you want to send the pageview event to all trackers, you would simply do:
var allTrackers = ga.getAll();
for(var i=0; i<allTrackers.length; i++) {
ga.send(allTrackers[i].getName()+".send", "pageview", {page: '/a/path', title: 'A Title'});
}
This works great for most of my pageview events. However, there is a race condition between when ui-router fires the initial view's $stateChangeSuccess (which is where I trigger the GA pageview), and when analytics.js is loaded.
Prior to analytics.js being loaded, Google Analytic's snippet creates a faux ga object, that you can send events to. This faux object does not have the rest of the ga functions on it, so you can not run getAll. Without the getAll function, I cannot get the tracker name and I cannot send pageview events.
As far as I can tell, Google Analytics does not provide any callbacks or events for when analytics.js is finished loading, so there is no way to tell when I will be able to start sending events. Right now I am using an $interval to check for the existence of ga.getAll, but that is not a very performant or ideal solution. This is what I've got:
gaCheckInterval = setInterval(function() {
if(typeof(ga) !== 'undefined' && typeof(ga.getAll) == 'function') {
clearInterval(gaCheckInterval);
sendBackloggedEvents();
}
}, 200);
Is there any other way to recognize when analytics.js has finished loading? Or any other way to send events to a named tracker, without having access to getAll?
Attempting to configure and trigger individual trackers circumvents the purpose of using a tag manager. Instead do:
dataLayer.push({event:'spa.pageView', page:..., title:...});
Where:
dataLayer is optionally renamed in the gtm snippet
spa is a handy abbreviation for your app/project/company/whatever in case you need to distinguish its actions later.
page and title can be whatever you like, you will reference them by adding dataLayer macros in your GTM container.
Then, in the tag manager you configure:
rule of {{event}} ends with pageView.
dataLayer macros for the page, title you are pushing into the dataLayer.
UA Tag (and later whatever else) to fire (1) and use the macros in (2) for the TAG parameters they override.
Repeat (3) as many times as you like for different UA properties with additional blocking rules, alternate macros or more granular firing rules as necessary.
Now you can configure the specifics and add other tag types that reuse the rules and macros without modifying the application for each change.
I'm pretty new to Angular and I'm using firebase as my backend. I was hoping someone could debug this issue. When I first go to my page www.mywebsite.com/#defaultHash the data doesn't load into the DOM, it does after visiting another hash link and coming back though.
My controller is like this:
/* initialize data */
var fb = new Firebase('https://asdf.firebaseio.com/');
/* set data to automatically update on change */
fb.on('value', function(snapshot) {
var data = snapshot.val();
$scope.propertyConfiguration = data.products;
console.log($scope.propertyConfiguration);
console.log("Data retrieved");
});
/* save data on button submit */
$scope.saveConfigs = function(){
var setFBref = new Firebase('https://asdf.firebaseio.com/products');
setFBref.update($scope.propertyConfiguration);
console.log("configurations saved!");
};
I have 3 hash routes say "Shared", "Registration", and "Home" with otherwise.redirectTo set to "Shared".(They all use this controller) Here's the error that occurs: (all "links" are href="#hashWhereever")
1) Go to website.com/#Shared or just refresh. Console logs $scope.propertyConfiguration and "Data Retrieved". DOM shows nothing.
2) Click to website.com/#Registration, console logs $scope data properly, DOM is loaded correctly.
3) Click back to website.com/#Shared, console logs $scope data properly yet this time DOM loads correctly.
4) Refresh currently correctly loaded website.com/#Shared. DOM elements disappear.
Since $scope.data is correct in all the cases here, shouldn't Angular make sure the DOM reflects the model properly? Why is it that the DOM loads correctly only when I am clicking to the page from another link.
I can "fix" it by adding window.location.hash = "Shared" but it throws a huge amount of errors in the console.
FIXED:(sorta)
The function $scope.$apply() forces the view to sync with the model. I'd answer this question myself and close it but I'm still wondering why the view doesn't load correctly when I correctly assign a value to $scope. If Angular's "dirty checking" checks whenever there is a possibility the model has changed, doesn't assigning a value to $scope overqualify?
Angular has no way to know you've assigned a value to $scope.variable. There's no magic here. When you run a directive (ng-click/ng-submit) or Angular internal functions, they all call $apply() and trigger a digest (a check of the dirty flags and update routine).
A possibly safer approach than $apply would be to use $timeout. Currently, if you call a write op in Firebase, it could synchronously trigger an event listener (child_added, child_changed, value, etc). This could cause you to call $apply while still within a $apply scope. If you do this, an Error is thrown. $timeout bypasses this.
See this SO Question for a bit more on the topic of digest and $timeout.
This doc in the Angular Developer Guide covers how compile works; very great background read for any serious Angular dev.
Also, you can save yourself a good deal of energy by using the official Firebase bindings for Angular, which already take all of these implementation details into account.
Vaguely Related Note: In the not-too-distant future, Angular will be able to take advantage of Object.observe magic to handle these updates.