notebook

都内でWEB系エンジニアやってます。

AngularでGoogleAnalyticsの計測コードを埋め込む

今回はGoogleAnalyticsのコードをAngularのプロジェクトに埋め込む話

SPAなので単に計測コードを貼り付けただけでは計測できないのでよしなにする必要がある

ライブラリも探せばあったがあまり頻繁に更新されてなかったりとちょっと不安要素があったので今回は自前で入れてみる

ただ単に入れましたーみたいな記事は探せば結構あったので今回はstrict: trueでも導入できるようにする

始めに

GoogleAnalyticsの管理画面から取得できる計測コードは下記(2019年10月現在)

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxxxxxxx-x"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-xxxxxxxx-x');
</script>

単純に計測コードをindex.htmlに貼るだけだととりあえず初回のロード時だけは計測できる、が…初回ロードだけ計測できても…って感じではありますね

今回は下記2点を実現したいので工夫する必要がある

  • 開発時は何も設定しない+本番のときのみtrackingのIDを入れ込みたい
  • ページ遷移ごとに計測をしたい

SPAに関しての公式のページを見ると

gtag.js を使用した単一ページ アプリケーションの測定  |  ウェブ向けアナリティクス(gtag.js)

developers.google.com

コンテンツが動的に読み込まれてアドレスバーの URL が更新された時点で、gtag.js で保存されているページ URL も更新する必要があります。

とあります

ということでページが変わったらその都度configを指定する必要があるようです

環境によってIDを読み込ませる

まずは、本番と開発でIDを切り替える(開発では入れない)ようにする

environment.tsgaCodeという名前でIDを記述する

  • src/environments/environments.ts
export const environment = {
  production: false,
  gaCode: ''
};
  • src/environments/environment.prod.ts
export const environment = {
  production: true,
  gaCode: 'UA-xxxxxxxx-x'
};

Angular側で本番のときはprodに記述した内容を適用してくれるので今回はこんな感じでOK

scriptタグの動的差し込み

素直にapp.component.htmlに計測タグを貼り付けてしまうとAngularがコンパイル時にscriptタグを除いてしまうのでコンポーネント読み込み時に動的に差し込む必要がある

  • app.component.ts(一部抜粋)
export class AppComponent implements OnInit {
  constructor(
    private renderer: Renderer2,
    @Inject(DOCUMENT) private document: Document
  ){
  }
  ngOnInit(){
    const s1 = this.renderer.createElement('script');
    s1.type = 'text/javascript';
    s1.src = `https://www.googletagmanager.com/gtag/js?id=${environment.gaCode}`;
    this.renderer.appendChild(this.document.head, s1);

    const s2 = this.renderer.createElement('script');
    s2.type = 'text/javascript';
    s2.text = `window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments)};gtag('js', new Date());gtag('config', '${environment.gaCode}');`;
    this.renderer.appendChild(this.document.head, s2);
  }
}

AngularのRenderer2経由でscriptタグを差し込む

型定義を読み込む

1から型の定義をしていくのはつらいのですでに型定義されているものがないか探してみたところ下記がそれっぽい感じ

DefinitelyTyped/index.d.ts at master · DefinitelyTyped/DefinitelyTyped

github.com

  • install
npm install --save-dev @types/gtag.js

中身を見るとgtagexportされていないのでよしなに型情報を引っ張りだしてくる必要はあるがなんとかできそう

tsconfig.json · TypeScript

www.typescriptlang.org

ここを読む感じだとtypesに入れるとデフォルトでincludeしてくれるよう

gtag.jsの中身で var gtagを定義しているので自動で読み込んだらどこでもgtagを参照できるようになる

  • tsconfig.app.json
{
  "compilerOptions": {
    "types": ["gtag.js"]
  }
}

これでgtagが呼び出せる様になったので

type Gtag = typeof gtag;

とすればGtagの型が取り出せる

あとはよしなにページが切り替わったタイミングでconfigのパスを書き換える

  • app.component.ts
import { Component, Inject, OnInit, Renderer2 } from '@angular/core';
import { environment } from '../environments/environment';
import { DOCUMENT } from '@angular/common';
import { NavigationEnd, Router } from '@angular/router';

type Gtag = typeof gtag;
type WindowWithGtag = Window & { gtag: Gtag };

.....
.....
.....
export class AppComponent implements OnInit {
  private windowWithGtag!: WindowWithGtag;

  constructor(
    private renderer: Renderer2,
    private router: Router,
    @Inject(DOCUMENT) private document: Document
  ) {
    this.windowWithGtag = window as WindowWithGtag;
  }

  ngOnInit(){
.....
.....
.....
    this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        this.windowWithGtag.gtag('config', environment.gaCode, {
          page_path: event.urlAfterRedirects
        });
      }
    });
    const s1 = this.renderer.createElement('script');
    s1.type = 'text/javascript';
    s1.src = `https://www.googletagmanager.com/gtag/js?id=${environment.gaCode}`;
    this.renderer.appendChild(this.document.head, s1);

    const s2 = this.renderer.createElement('script');
    s2.type = 'text/javascript';
    s2.text = `window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments)};gtag('js', new Date());gtag('config', '${this.gaCode}');`;
    this.renderer.appendChild(this.document.head, s2);
  }

page_pathに渡している部分urlAfterRedirectsはAngular側でリダイレクトした場合のリダイレクト後のパス

f:id:swfz:20191020231456p:plain

無事ページごとのアクセスを計測することが可能になりました

まとめ

今回はGoogleAnalyticsを題材として下記を行った

  • 型定義情報を引っ張りだす(exportされてないものでも取得できる)
  • windowオブジェクトに特定のプロパティを追加してTypeScript側から呼び出せるようにする

計測系のコードはwindowオブジェクトに対して色々追加してよしなにやるっていうパターンが多いので、この方法であれば他の計測コードとかも使えるかと思います