To solve the problem, I created my own implementation of Google Tag Manager with manual calls to Google Analytics.
I use React (18.0.9) with TypeScipt
type WindowWithDataLayer = Window & {
dataLayer: Record<string, any>[]
}
declare const window: WindowWithDataLayer
/**
* Provider for Google analytics with Google Tag Manager
*/
export default class GoogleTagManagerProvider implements IAnalyticsProvider {
/**
* The global unique id of provider
*/
GUID = 'GoogleTagManager'
private _id: string
/**
* Create a new instance of GoogleTagManagerProvider
* @param id The measurement ID
*/
constructor(id: string) {
this._id = id
window.dataLayer = window.dataLayer || []
}
/**
* Initialize the analytics provider
*/
initialize(): void {
// Default consent mode is "denied" for both ads and analytics as well as the optional types, but delay for 2 seconds until the Cookie Solution is loaded
this.gtag({
consent: 'default',
ad_storage: 'denied',
analytics_storage: 'denied',
functionality_storage: 'denied', // optional
personalization_storage: 'denied', // optional
security_storage: 'denied', // optional
wait_for_update: 2000 // milliseconds
})
// Improve ad click measurement quality (optional)
this.gtag({
set: {
url_passthrough: true
}
})
// Further redact your ads data (optional)
this.gtag({
set: {
ads_data_redaction: true
}
})
this.gtag({
js: new Date()
})
this.gtag({
config: this._id,
debug_mode: IsDevelopment
})
window.dataLayer.push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
})
var f = document.getElementsByTagName('script')[0]
var j = document.createElement('script')
j.async = true
j.src = `https://www.googletagmanager.com/gtm.js?id=${this._id}`
if (f.parentNode) {
f.parentNode.insertBefore(j, f)
}
}
/**
* Informs the provider that the consent for the user's data has changed
* @param consent The user consent
*/
onConsentChanged(consent: IConsent) {
// Alredy handled by iubenda
}
/**
* Fire the page view event
*/
pageView(): void {
this.gtag({
event: 'page_view',
page_title: document.title,
page_location: window.location.href
})
}
/**
* Fire login event
* @param method The login event such as Email, Google or Facebook
*/
login(method: string) {
this.gtag({
event: 'login',
method: method
})
}
/**
* Fire sign up event
* @param method The login event such as Email, Google or Facebook
*/
signUp(method: string) {
this.gtag({
event: 'sign_up',
method: method
})
}
/**
* Fire the add to cart event
* @param product The product added to cart
*/
addToCart(product: IProduct): void {
const totalAmount = PriceUtility.calculateTotalAmount(product.price)
this.gtag({
event: 'add_to_cart',
ecommerce: {
currency: 'EUR',
value: totalAmount,
items: [
{
item_id: product.id,
item_name: product.name,
discount: product.price.amount - totalAmount,
price: product.price.amount,
quantity: 1
}
]
}
})
}
/**
* Fire the remove from cart event
* @param product The product removed to cart
*/
removeFromCart(product: IProduct) {
const totalAmount = PriceUtility.calculateTotalAmount(product.price)
this.gtag({
event: 'remove_from_cart',
ecommerce: {
currency: 'EUR',
value: totalAmount,
items: [
{
item_id: product.id,
item_name: product.name,
discount: product.price.amount - totalAmount,
price: product.price.amount,
quantity: 1
}
]
}
})
}
/**
* Fire the begin checkout event
* @param cart The cart
*/
beginCheckout(cart: ICart) {
const items: any[] = []
cart.details.forEach((value, index) => {
const totalAmount =
PriceUtility.calculateTotalAmount(value.product.price) * value.quantity
const baseAmount = value.product.price.amount * value.quantity
const totalDiscount = baseAmount - totalAmount
items.push({
item_id: value.product.id,
item_name: value.product.name,
discount: totalDiscount,
price: baseAmount,
quantity: value.quantity
})
})
this.gtag({
event: 'begin_checkout',
value: items.reduce(
(previousValue, currentValue) =>
previousValue + (currentValue.price - currentValue.discount),
0
),
currency: 'EUR',
items: items
})
}
/**
* Fire the purchase event
* @param transactionId The payment transaction Id
* @param cart The cart
*/
purchase(transactionId: string, cart: ICart) {
const items: any[] = []
cart.details.forEach((value, index) => {
const totalAmount =
PriceUtility.calculateTotalAmount(value.product.price) * value.quantity
const baseAmount = value.product.price.amount * value.quantity
const totalDiscount = baseAmount - totalAmount
items.push({
item_id: value.product.id,
item_name: value.product.name,
discount: totalDiscount,
price: baseAmount,
quantity: value.quantity
})
})
this.gtag({
event: 'purchase',
transaction_id: transactionId,
value: items.reduce(
(previousValue, currentValue) =>
previousValue + (currentValue.price - currentValue.discount),
0
),
currency: 'EUR',
items: items
})
}
/**
* Fire the join group event
* @param groupId
*/
joinGroup(groupId: string) {
this.gtag({
event: 'join_group',
group_id: groupId
})
}
gtag(args: any) {
window.dataLayer.push(args)
}
}