67

I'm trying to use the Google Analytics with angular 4, but i can't find any @type to ga.js in ts.

For a quick solution I used this in every component:

declare let ga: any;

Following how I resolved it:

Create a function to load the GA dynamically that inserts the GA script with 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);
    }

Create the service to implement the methods that you will need.

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);
    }
}

Then I finally use the service injected in component.

constructor(private ga: GAService) {}

ngOnInit() { this.ga.sendPageView('/join'); }
Leonardo Oliveira
  • 1,289
  • 1
  • 12
  • 14
  • Kind of broad question here. As there are many ways to handle third-party globals in Angular, but if you want a module for analytics. Give this a try: https://github.com/peaksandpies/universal-analytics – Reactgular Aug 18 '17 at 14:21
  • Sorry about the broad question, I understand that we have many ways to handle third-party globals, but I've been resolving this type of issue with the '@types' and angular injectionToken, then for this case it would be better to find a '@type' with export module. Finally thank you for your help and i will check your solution. – Leonardo Oliveira Aug 18 '17 at 14:38
  • 1
    Maybe if you updated the question to explain how your using `ga` and what problems that is giving you. For example; using `Function` as the type means that it has a void return value. In my experience, outside variables like that are best left as `define let ga: any;` which kind of disables all type checking. Making it easier to work with. – Reactgular Aug 18 '17 at 15:20
  • Thanks @cgTag, I replaced the function type by any type and created a service to declare **GA** only once and now it looks good to me. As you requested, I updated the question to explain how I did it. – Leonardo Oliveira Aug 18 '17 at 19:12
  • 1
    Is that a question or an answer? –  Sep 26 '17 at 12:26

10 Answers10

102

First of all, you need to install typings for Google Analytics in your devDependencies

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

Then add your tracking code in the base index.html, and remove the last line as shown bellow:

<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>

The next step consists to update your home component constructor 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 want to track some specific event, you can also create a service and inject it into any component that 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 });
  }
}

So if you want track a click on your home component for example, all you need to do is to inject the GoogleAnalyticsEventsService and call the emitEvent() method.

The updated home component source code:

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);
  }
Laiso
  • 2,528
  • 3
  • 13
  • 30
  • 4
    with Angular 4, I got his error `Cannot find name 'ga'. webpack: Failed to compile.` in the terminal for this code `if (event instanceof NavigationEnd) { ga('set', 'page', event.urlAfterRedirects); ga('send', 'pageview'); }` – rattanak Oct 09 '17 at 23:52
  • 6
    Try to add declaration inside your component: `declare var ga: Function;` – Laiso Oct 10 '17 at 09:12
  • 9
    Shouldn't `npm install --save-dev @types/google.analytics` fix the need of `declare var ga: Function;`? – Remi Sture Nov 06 '17 at 10:05
  • 1
    If you installed the types then remove "declare var ga" – Mackelito Nov 20 '17 at 14:13
  • Even after installing typing I need to add "declare let ga: any; – Anthony Dec 04 '17 at 19:28
  • How can i use **Ecommerce Tracking** I am using like below public ecommerceAddTransaction( orderID: string, revenue: string) { ga('ecommerce:addTransaction', { id: orderID, affiliation: 'test', revenue: revenue, shipping: '', tax: '0', currency: 'INR' }); ga('ecommerce:addItem', { id: orderID, package_name: 'test package', price: revenue, quantity: '1', currency: 'INR' }); ga('ecommerce:send'); } @Laiso – khurshed alam Feb 28 '18 at 13:04
  • 1
    I am still getting the ga issue. even when adding `let ga: any;`. I have the types installed, but it's not working. Can someone help? – r3plica Mar 02 '18 at 11:23
  • 1
    Was this ever resolved? I am currently getting same issue. ReferenceError: ga is not defined. Using Angular 5, also tried installing the @types – Taranjit Kang Apr 05 '18 at 16:44
  • 4
    I solved the ReferenceError by adding an import statement: `import {} from '@types/google.analytics';` – Dima Nevelev Apr 26 '18 at 05:21
  • I have tried adding the @types,/google.analytics and still getting the reference error. Tried import {} from '@types/google.analytics'; – Taranjit Kang May 11 '18 at 18:57
  • I solved, added in my index, as well as import {} from '@types/google.analytics'; – Taranjit Kang May 11 '18 at 19:20
  • 4
    Regarding the reference error, you could try removing the empty `types` array from my `tsconfig.json` file, which worked for me. From the docs: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html ` `Specify "types": [] to disable automatic inclusion of @types packages.` So removing the empty array means that global declarations in @types packages will be automatically included without the need to specifically import them in any file. – lemmingworks May 23 '18 at 20:05
  • Is it necessary to add the `UA-ID` in the separate script? I would like to keep my configs simple and use angular environments. Is it ok, to set the id in the main component's constructor? – fodma1 Jun 27 '18 at 05:56
  • I want to send different eventLabel for each button. Can someone share the code for that? – Kushal J. Nov 01 '18 at 07:50
  • 1
    @KushalJayswal You have to call the `emitEvent` method on each button click call and pass through it the `eventLabel` you want to send. – Laiso Nov 02 '18 at 12:59
  • Will this work with gtag.js which is the current (default) implementation when creating a Google Analytics property tracking code? Can I copy the gtag snippet instead of this analytics.js parameterized function and have it work in the same way? – Meliovation Nov 22 '18 at 14:34
  • Today i learned that when you are setting the page, if you are using matrix params, ga seems to trim the path (event.urlAfterRedirects) after the first semicolon (ie /app/component-1;param=value/component-2 becomes /app/component-1). For now i have worked around the issue by replacing the semicolons with ampersands... i havent been able to find any docs on what characters are supported – ekill May 03 '19 at 17:35
  • @Laiso Can we track the time for every page in which the user stays in? – Sai Manoj Aug 14 '20 at 10:37
53

I'm surprised nobody here mentioned Google's Tag Manager yet (which is the version of the script that the Google Analytics console outputs for me in the last few years, whenever I add a new identity).

Here's a solution that I came up with today, which is a variation of the solutions already mentioned in the other answers, adapter to Google's Tag Manager script. I think it would be useful for people who migrated from ga() to gtag() (a migration that is recommended as far as I know).

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:

  • Declare the service in the imports[] section of your app.module.ts.
  • In your app.component.ts (or whichever higher level component holds the <router-outlet> tag in its template), inject the AnalyticsService and call this.analytics.init() as early as possible (e.g. ngOnInit)
  • In the environment.ts (in my case - environment.prod.ts), add the Analytics ID as googleAnalyticsKey: 'UA-XXXXXXX-XXXX'
Dzhuneyt
  • 7,039
  • 11
  • 56
  • 108
  • 1
    Thanks a lot, I don't know why nobody mention gtag either. Very good solution, you're a star! – Tonio Apr 17 '19 at 16:55
  • 1
    Thanks for this solution. 1 small mistake though. services declaration need to be changed like below. Declare the service in the `providers[]` section of your `app.module.ts`. – Shabith May 17 '19 at 06:43
  • 2
    @Shabith actually [`providedIn` supercedes `providers: []` in the Module as the recommended practice, starting with Angular 6+](https://medium.com/@tomastrajan/total-guide-to-angular-6-dependency-injection-providedin-vs-providers-85b7a347b59f). Personally, I still prefer the "cleanliness" of the `providers` approach, but who asks me. :) – Dzhuneyt May 23 '19 at 10:47
  • @Dzhuneyt actually you are right! I'm new to Angular and I was not aware of the `providedIn` approach. – Shabith May 24 '19 at 06:31
  • 1
    @Dzhuneyt Great solution, thanks. But when I add the service to the imports[] I get `Unexpected value 'AnalyticsService' imported by the module 'AppModule'. Please add a @NgModule annotation.`. It works without it. – Klemens Zleptnig Aug 30 '19 at 15:24
  • Don't know does anyone has same problem, but after adding this my navigation is messed up. Clicking on a link doesn't take me to top of page, instead it opens other component html but in same section as it was on current page. So if I click on link that is on the end of component1 html and it loads content from component2, but I'm on bottom of that, not on top. Hope someone will understand what am I talking about :) Solved it by adding `window.scrollTo(0,0);` at the end of `listenForRouteChanges`, after `console.log('Property ID', environment.googleAnalyticsKey);` – BeRightBack Oct 22 '19 at 20:50
  • This solution is almost complete, I think it needs a check to see if there is a key in environment before it initializes and inside of the event member. – Bill Barry Aug 17 '20 at 13:33
24

Load google analytics, using environment vars, the async way;

(Works on Angular 5)

(Using @Laiso answer)

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 whatever component:

 // ... import stuff

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

 // ... 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);
    }
  }

// Somewhere else we can emit a new ga event
this.googleAnalyticsService.emitEvent("testCategory", "testAction", "testLabel", 10);
Community
  • 1
  • 1
Artipixel
  • 1,206
  • 12
  • 17
  • 2
    The google-analytics.service.ts is a good solution, but note that one downside of moving the appendGaTrackingCode() from the recommended index.html into the app.component means google analytics won't accurately measure average page load times, since it'll start the timer only after the angular javascript bundle is retrieved and executing. – user761574 Aug 24 '18 at 17:04
  • @user761574 That is correct, but only for the first load of the application, assuming you share this service across your application. – Artipixel Aug 25 '18 at 21:44
7

GoogleAnalyticsService

You can create service that subscribes for router events and inject it in app.module.ts so you don't have to inject it in every component.

@Injectable()
export class GoogleAnalyticsService {

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

Here is tutorial (my blog).

Filip Molcik
  • 809
  • 9
  • 10
5

To avoid any type checking if ga is defined globally at window level then you could simply do

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

Hope it helps.

jalak vora
  • 129
  • 2
  • 9
4

Personally I've found it quite easy simply:

  • Adding the GA tracking code after my <app-root> in index.html (as shown above)
  • Including Angulartics for GA into my application (GA Example here)

In my app.component.ts I've added this:

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));
    }
}

It's not much different to the above, but makes it much easier for testing.

Jim Drury
  • 79
  • 1
  • 4
1

I suggest embedding the Segment script into your index.html and extend analytics library onto the window object:

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

Then add tracking calls onto 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');
  }
}

I’m the maintainer of https://github.com/segmentio/analytics-angular. I recommend checking it out if you want to solve this problem by using one singular API to manage your customer data, and be able to integrate into any other analytics tool (we support over 250+ destinations) - without writing any additional code.

William
  • 61
  • 3
0

You might be helped

app.component.ts

declare let gtag: Function;

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

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

});
0

index.html file

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

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

......
</head>

AppComponent

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 AppComponent implements OnInit {
  title = 'angular-app';

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

}
Parth kharecha
  • 3,353
  • 2
  • 18
  • 31
-2

Do you have include the type in "types" compilerOptions of tsconfig.app.json?

I had the same problem and solved it including "google.analytics" (see, not "@ types/google.analytics") in tsconfig.app.json