I'm trying to add Google Analytics to my Angular app without having to add the scripts manually to the index file. My goal is to use a service that has functions you can call to initialize the analytics and track page views. Here's the main part of the service:
export class GoogleAnalyticsService {
private googleAnalyticsId: string;
private renderer2: Renderer2;
private scriptsLoaded: boolean = false;
constructor(
private rendererFactory2: RendererFactory2,
@Inject(DOCUMENT) private _document: Document,
private _config: RuntimeConfigLoaderService,
private _router: Router,
) {
this.renderer2 = this.rendererFactory2.createRenderer(null, null);
this.googleAnalyticsId = this._config.getConfigObjectKey('googleAnalyticsId');
}
init() {
this.insertMainScript();
}
private insertMainScript() {
if (this.googleAnalyticsId) {
const script: HTMLScriptElement = this.renderer2.createElement('script');
script.type = 'text/javascript';
script.onload = this.insertSecondHalfOfScript.bind(this);
script.src = `https://www.googletagmanager.com/gtag/js?id=${this.googleAnalyticsId}`;
script.text = '';
this.renderer2.appendChild(this._document.body, script);
}
}
private insertSecondHalfOfScript() {
const script: HTMLScriptElement = this.renderer2.createElement('script');
script.type = 'text/javascript';
script.src = '';
script.text = `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
`;
this.renderer2.appendChild(this._document.body, script);
this.scriptsLoaded = true;
}
trackSinglePageView(event: NavigationEnd) {
if (this.googleAnalyticsId && this.scriptsLoaded) {
console.log('logging pageview');
gtag('config', this.googleAnalyticsId, { page_path: event.urlAfterRedirects });
}
}
trackPageViews() {
return this._router.events.pipe(
filter((evt: RouterEvent) => evt instanceof NavigationEnd),
tap((event: NavigationEnd) => {
this.trackSinglePageView(event);
}),
);
}
}
The main function to call from the AppComponent is init. That will inject the scripts onto the page. As I watch the application load, I see those scripts get added. I also see the https://www.googletagmanager.com/gtag/js script load in the Network tab of the developer tools. So the script is definitely there.
Then after calling the init function in the AppComponent I call the trackPageViews function and subscribe to the returned Observable. I filter out all the router events until the above scripts are loaded, and then filter out all events that are not NavigationEnd events. If the scripts are loaded and it's a NavigationEnd event, then the trackSinglePageView function and gtag is called with the necessary data. However, when this happens, I get the following error:
core.js:6189 ERROR ReferenceError: gtag is not defined
at GoogleAnalyticsService.trackSinglePageView (google-analytics.service.ts:58)
at TapSubscriber._tapNext (google-analytics.service.ts:69)
at TapSubscriber._next (tap.js:40)
at TapSubscriber.next (Subscriber.js:49)
at FilterSubscriber._next (filter.js:33)
at FilterSubscriber.next (Subscriber.js:49)
at Subject.next (Subject.js:39)
at SafeSubscriber._next (router.js:7593)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
If I don't use the service to load the analytics scripts and put them in the head of the app's index.html file and just track the page views, then I don't get any errors in the console. So it appears that although the scripts are loaded, gtag is still not defined unless the analytics scripts are in the head.
Is there any way to get around this? Am I doing something incorrect here? Any ideas greatly appreciated.