Leveraging Google Analytics with Angular 4 and beyond

Having trouble integrating Google Analytics with Angular 4? Can't seem to find the @type for ga.js in ts?

Here's a quick fix that I implemented in every component:

declare let ga: any;

How did I solve it, you ask?

I created a function to dynamically load GA by inserting the GA script with the current trackingId and user.

    loadGA(userId) {
        if (!environment.GAtrackingId) return;

        let scriptId = 'google-analytics';

        if (document.getElementById(scriptId)) {
            return;
        }

        var s = document.createElement('script') as any;
        s.type = "text/javascript";
        s.id = scriptId;
        s.innerText = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create', { trackingId: '" + **environment.GAtrackingId** + "', cookieDomain: 'auto', userId: '" + **userId** + "'});ga('send', 'pageview', '/');";

        document.getElementsByTagName("head")[0].appendChild(s);
    }

Additionally, I created a service to implement the necessary methods.

import { Injectable } from '@angular/core';
import { environment } from '../../../environments/environment';

declare let ga: any;

@Injectable()
export class GAService {
    constructor() {
    }

    /**
     * Checks if the GA script was loaded.
     */
    private useGA() : boolean { 
        return environment.GAtrackingId && typeof ga !== undefined;
    }

    /**
     * Sends the page view to GA.
     * @param  {string} page The path portion of a URL. This value should start with a slash (/) character.
     */
    sendPageView(
        page: string
    ) {
        if (!this.useGA()) return;
        if (!page.startsWith('/')) page = `/${page}`;      
        ga('send', 'pageview', page);
    }


    /**
     * Sends the event to GA.
     * @param  {string} eventCategory Typically the object that was interacted with (e.g. 'Video')
     * @param  {string} eventAction The type of interaction (e.g. 'play')
     */
    sendEvent(
        eventCategory: string,
        eventAction: string
    ) { 
        if (!this.useGA()) return;
        ga('send', 'event', eventCategory, eventAction);
    }
}

Finally, simply inject the service into your component and utilize it.

constructor(private ga: GAService) {}

ngOnInit() { this.ga.sendPageView('/join'); }

Answer №1

To begin, the first step is to include typings for Google Analytics in your project's devDependencies

npm install --save-dev @types/google.analytics

After that, insert your tracking code into the base index.html, making sure to remove the final line as indicated below:

<body>
  <app-root>Loading...</app-root>
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-XXXXXX-ID', 'auto');  // <- add the UA-ID 
                                           // <- remove the last line 
  </script>
</body>

Next, update the constructor of your home component for event tracking.

constructor(public router: Router) {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        ga('set', 'page', event.urlAfterRedirects);
        ga('send', 'pageview');
      }
    });
  }

If you wish to track specific events, you can create a service and inject it into any component where you want to implement event tracking.

// ./src/app/services/google-analytics-events-service.ts

import {Injectable} from "@angular/core";

@Injectable()
export class GoogleAnalyticsEventsService {

  public emitEvent(eventCategory: string,
                   eventAction: string,
                   eventLabel: string = null,
                   eventValue: number = null) {
    ga('send', 'event', { eventCategory, eventLabel, eventAction, eventValue });
  }
}

For example, to track a click on your home component, simply inject the GoogleAnalyticsEventsService and call the emitEvent() method.

The revised source code for the home component:

constructor(public router: Router, public googleAnalyticsEventsService: GoogleAnalyticsEventsService) {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        ga('set', 'page', event.urlAfterRedirects);
        ga('send', 'pageview');
      }
    });
  }
  submitEvent() { // event fired from home.component.html element (button, link, ... )
    this.googleAnalyticsEventsService.emitEvent("testCategory", "testAction", "testLabel", 10);
  }

Answer №2

Surprisingly, no one has brought up the use of Google's Tag Manager yet when discussing scripts like the one generated by Google Analytics console in recent years whenever a new identity is added.

Today, I have devised a solution that builds upon existing methods mentioned in other responses, specifically tailored to work with Google's Tag Manager script. This solution may prove beneficial for individuals transitioning from using ga() to gtag(), a migration process that is strongly recommended.

analytics.service.ts

declare var gtag: Function;

@Injectable({
  providedIn: 'root'
})
export class AnalyticsService {

  constructor(private router: Router) {

  }

  public event(eventName: string, params: {}) {
    gtag('event', eventName, params);
  }

  public init() {
    this.listenForRouteChanges();

    try {

      const script1 = document.createElement('script');
      script1.async = true;
      script1.src = 'https://www.googletagmanager.com/gtag/js?id=' + environment.googleAnalyticsKey;
      document.head.appendChild(script1);

      const script2 = document.createElement('script');
      script2.innerHTML = `
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', '` + environment.googleAnalyticsKey + `', {'send_page_view': false});
      `;
      document.head.appendChild(script2);
    } catch (ex) {
      console.error('Error appending google analytics');
      console.error(ex);
    }
  }

  private listenForRouteChanges() {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        gtag('config', environment.googleAnalyticsKey, {
          'page_path': event.urlAfterRedirects,
        });
        console.log('Sending Google Analytics hit for route', event.urlAfterRedirects);
        console.log('Property ID', environment.googleAnalyticsKey);
      }
    });
  }
}

Prerequisites:

  • Include the service in the imports[] section of your app.module.ts.
  • In your app.component.ts (or any higher level component containing the <router-outlet> tag in its template), inject the AnalyticsService and call this.analytics.init() at the earliest opportunity (such as in ngOnInit).
  • In the environment.ts file (e.g., environment.prod.ts), add the Analytics ID as
    googleAnalyticsKey: 'UA-XXXXXXX-XXXX'
    .

Answer №3

Implementing Google Analytics with Environment Variables in Angular 5 asynchronously

(Compatible with Angular 5)

(Adapted from @Laiso's solution)

google-analytics.service.ts

import {Injectable} from '@angular/core';
import {NavigationEnd, Router} from '@angular/router';
declare var ga: Function;

@Injectable()
export class GoogleAnalyticsService {

  constructor(public router: Router) {
    this.router.events.subscribe(event => {
      try {
        if (typeof ga === 'function') {
          if (event instanceof NavigationEnd) {
            ga('set', 'page', event.urlAfterRedirects);
            ga('send', 'pageview');
            console.log('%%% Google Analytics page view event %%%');
          }
        }
      } catch (e) {
        console.log(e);
      }
    });

  }


  /**
   * Emit google analytics event
   * Fire event example:
   * this.emitEvent("testCategory", "testAction", "testLabel", 10);
   * @param {string} eventCategory
   * @param {string} eventAction
   * @param {string} eventLabel
   * @param {number} eventValue
   */
  public emitEvent(eventCategory: string,
   eventAction: string,
   eventLabel: string = null,
   eventValue: number = null) {
    if (typeof ga === 'function') {
      ga('send', 'event', {
        eventCategory: eventCategory,
        eventLabel: eventLabel,
        eventAction: eventAction,
        eventValue: eventValue
      });
    }
  }


}

Inside app.component or any other component:

 // ... import statements

 import { environment } from '../../../environments/environment';

 // ... other declarations

 constructor(private googleAnalyticsService: GoogleAnalyticsService){
    this.appendGaTrackingCode();
 }

 private appendGaTrackingCode() {
    try {
      const script = document.createElement('script');
      script.innerHTML = `
        (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
        })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
       
        ga('create', '` + environment.googleAnalyticsKey + `', 'auto');
      `;
      document.head.appendChild(script);
    } catch (ex) {
     console.error('Error appending google analytics');
     console.error(ex);
    }
  }

// In another part of the code, we can trigger a new Google Analytics event
this.googleAnalyticsService.emitEvent("testCategory", "testAction", "testLabel", 10);

Answer №4

GoogleTrackingService

A custom service can be created to handle router events and then injected into the app.module.ts file, eliminating the need to inject it into every component individually.

@Injectable()
export class GoogleTrackingService {

  constructor(router: Router) {
    if (!environment.production) return; // <-- To activate GA only in production
    router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        ga('set', 'page', event.url);
        ga('send', 'pageview');
      }
    })
  }

Check out this tutorial on my blog for more information..

Answer №5

To skip the necessity for any type checking, you can use this method when ga is declared globally at the window level:

 window["ga"]('send', {
    hitType: 'event',
    eventCategory: 'eventCategory',
    eventAction: 'eventAction'
    });

I trust this information proves useful.

Answer №6

From my personal experience, I have found a simple way to implement Google Analytics:

  • Inserting the GA tracking code after the <app-root> in index.html (as demonstrated above)
  • Integrating Angulartics for Google Analytics into my application (See GA Example here)

In my app.component.ts, I made the following addition:

import {Component, OnInit} from '@angular/core';
import {NavigationEnd, Router} from '@angular/router';
import {Angulartics2GoogleAnalytics} from 'angulartics2/ga';
import {filter} from 'rxjs/operators';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
    constructor(private ga: Angulartics2GoogleAnalytics,
                 private router: Router) {
    }

    ngOnInit() {
        this.router.events
            .pipe(filter(event => event instanceof NavigationEnd))
            .subscribe((event: NavigationEnd) =>
                this.ga.pageTrack(event.urlAfterRedirects));
    }
}

This approach may seem similar to the above, but it significantly facilitates testing.

Answer №7

If you're looking to incorporate the Segment script into your index.html file and add the analytics library to the window object, here's a suggestion:

declare global {
  interface Window { analytics: any; }
}

Next, include tracking calls in the (click) event handler:

@Component({
  selector: 'app-signup-btn',
  template: `
    <button (click)="trackEvent()">
      Signup with Segment today!
    </button>
  `
})
export class SignupButtonComponent {
  trackEvent() {
    window.analytics.track('User Signup');
  }
}

As the maintainer of https://github.com/segmentio/analytics-angular, I encourage you to explore this solution for managing customer data using a single API that can seamlessly integrate with various analytics tools (supporting over 250+ destinations) without the need for extra code. 🙂

Answer №8

This method appears to be the most straightforward:

Once you've added the types by running this command:

npm install --save-dev @types/google.analytics

Don't forget to update tsconfig.json to include them:

// tsconfig.json
{
  types: ["google.analytics"]
}

Answer №9

You could receive assistance

app.component.ts

let gtag: Function;

this.router.events.subscribe(event => {

    if(event instanceof NavigationEnd) {
        gtag('config', '*****', {'page_path': event.urlAfterRedirects});
    }

});

Answer №10

website.html file

<head>
.........

    <script async src="https://www.googletagmanager.com/gtag/js?id=Tracking-ID-123"></script>
      <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
    </script>

......
</head>

MainComponent

import { Component, OnInit } from '@angular/core';
import {NavigationEnd, Router} from '@angular/router';
import {environment} from '../environments/environment';
// tslint:disable-next-line:ban-types
declare let gtag: Function;

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class MainComponent implements OnInit {
  title = 'angular-app';

  constructor(public router: Router) {
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        gtag('config', Tracking-ID-123, {'page_path': event.urlAfterRedirects});
      }
    });
  }

}

Answer №11

Have you made sure to add the type under "types" in the compilerOptions of your tsconfig.app.json file?

I encountered a similar issue and was able to resolve it by including "google.analytics" (not "@types/google.analytics") in my tsconfig.app.json

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountering "token_not_provided" error message on all GET routes in Laravel 5.3 with JWT authentication

I'm currently working on implementing authentication using Laravel 5.3 and Angular 2 with JWT. The authentication part is functioning properly, and I am able to successfully obtain the token. However, when attempting to navigate to any GET routes, an ...

Module '@tanstack/react-table' cannot be located even though it has been successfully installed

Currently, I am tackling a TypeScript React project and encountering an issue while attempting to import ColumnDef from @tanstack/react-table in my columns.tsx file. import { ColumnDef } from "@tanstack/react-table"; export type Payment = { id ...

`ionic CapacitorJS extension for Apache server`

Currently, we are developing a hybrid mobile app using Ionic Capacitors. In the initial stages, we started with an Ionic Cordova project and then upgraded it to an Ionic Capacitor project using the latest version. For network requests, we utilized the fol ...

Tips for presenting random images from an assortment of pictures on a webpage

I'm looking to enhance my website by adding a unique feature - a dynamic banner that showcases various images from a specific picture pool. However, I'm unsure of how to find the right resources or documentation for this. Can you provide any guid ...

Adding External Libraries to Angular-CLI: Expanding Your Toolkit with jQuery and Bootstrap

Right now I have to: Install local libraries using npm install bootstrap and npm install jquery Create a folder called src\assets Copy all files from node_modules/bootstrap and node_modules/jquery In index.html <script src="assets/jquery/jquery ...

Subscribing to Observables in Angular Services: How Using them with ngOnChanges Can Trigger Excessive Callbacks

Consider the following scenario (simplified): Main Component List Component List Service Here is how they are connected: Main Component <my-list [month]="month"></my-list> List Component HTML <li *ngFor="let item in list | async>&l ...

When you use array[index] in a Reduce function, the error message "Property 'value' is not defined in type 'A| B| C|D'" might be displayed

Recently, I delved deep into TypeScript and faced a challenge while utilizing Promise.allSettled. My objective is to concurrently fetch multiple weather data components (such as hourly forecast, daily forecast, air pollution, UV index, and current weather ...

Using MSAL for authentication in combination with NGRX for state management

I am currently using MSAL login, which automatically redirects to the Microsoft login page. However, when I set the cache location to 'none' instead of 'localStorage', it is not redirecting. I prefer not to store the token in local sto ...

The DefaultTheme in MaterialUI no longer recognizes the 'palette' property after transitioning from v4 to v5, causing it to stop functioning correctly

Currently in the process of transitioning my app from Material UI v4 to v5 and encountering a few challenges. One issue I'm facing is that the 'palette' property is not recognized by DefaultTheme from Material UI when used in makeStyles. Thi ...

Error: The argument provided is of type 'unknown', which cannot be assigned to a parameter of type 'string'. This issue arose when attempting to utilize JSON.parse in a TypeScript implementation

I'm currently converting this code from Node.js to TypeScript and encountering the following issue const Path:string = "../PathtoJson.json"; export class ClassName { name:string; constructor(name:string) { this.name = name; } ...

Creating a NgFor loop in Angular 8 to display a dropdown menu styled using Material

I'm currently facing an issue with incorporating a Materialize dropdown within a dynamically generated table using *ngFor. The dropdown does not display when placed inside the table, however, it works perfectly fine when placed outside. <p>User ...

Is there a way to retrieve the initial item of a JSON array from an HTML document using Angular 2?

Within the src/assets/ directory, I have a json file called product.json with the following structure: [ { "images": "http://openclipart.org/image/300px/svg_to_png/26215/Anonymous_Leaf_Rake.png", "textBox": "empty", "comments": "empty" }, { "i ...

Tips on transforming Angular 2/4 Reactive Forms custom validation Promise code into Observable design?

After a delay of 1500ms, this snippet for custom validation in reactive forms adds emailIsTaken: true to the errors object of the emailAddress formControl when the user inputs [email protected]. https://i.stack.imgur.com/4oZ6w.png takenEmailAddress( ...

Is it possible to combine TypeScript modules into a single JavaScript file?

Hey there, I'm feeling completely lost with this. I've just started diving into Typescript with Grunt JS and I could really use some assistance. I already have a Grunt file set up that runs my TS files through an uglify process for preparing the ...

Using TypeScript's type casting functionality, you can easily map an enum list from C#

This is a C# enum list class that I have created: namespace MyProject.MyName { public enum MyNameList { [Description("NameOne")] NameOne, [Description("NameTwo")] NameTwo, [Description("NameThree")] NameThree ...

The term "Exports" has not been defined

I'm currently facing a challenge trying to develop an Angular application based on my initial app. The process is not as smooth as I had hoped. Here's the current setup: index.html <!DOCTYPE html> <html> <head> <base h ...

Unable to perform a default import in Angular 9 version

I made adjustments to tsconfig.json by adding the following properties: "esModuleInterop": true, "allowSyntheticDefaultImports": true, This was done in order to successfully import an npm package using import * as ms from "ms"; Despite these changes, I ...

Tips on extracting value from a pending promise in a mongoose model when using model.findOne()

I am facing an issue: I am unable to resolve a promise when needed. The queries are executed correctly with this code snippet. I am using NestJs for this project and need it to return a user object. Here is what I have tried so far: private async findUserB ...

Developing an array in Angular on an Android device is proving to be a sluggish

I'm facing an issue with my simple array that collects rows from a database and uses a distance column as a key. let output = {}; for (let dataRow of sqllite.rows) { output[dataRow.distance] = dataRow; } When testing in Chrome browser on my PC, ...

In Typescript, it is not possible to assign the type 'any' to a string, but I am attempting to assign a value that is

I'm new to TypeScript and currently learning about how types function in this language. Additionally, I'm utilizing MaterialUI for this particular project. The issue I'm encountering involves attempting to assign an any value to a variable ...