├── .browserslistrc
├── .editorconfig
├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ ├── publish.yml
│ └── tests.yml
├── .gitignore
├── .npmrc
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── angular.json
├── e2e
├── protractor.conf.js
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
└── tsconfig.e2e.json
├── package-lock.json
├── package.json
├── projects
└── ngx-google-analytics
│ ├── karma.conf.js
│ ├── ng-package.json
│ ├── package.json
│ ├── src
│ ├── index.ts
│ ├── lib
│ │ ├── directives
│ │ │ ├── ga-event-category.directive.spec.ts
│ │ │ ├── ga-event-category.directive.ts
│ │ │ ├── ga-event-form-input.directive.spec.ts
│ │ │ ├── ga-event-form-input.directive.ts
│ │ │ ├── ga-event.directive.spec.ts
│ │ │ └── ga-event.directive.ts
│ │ ├── enums
│ │ │ └── ga-action.enum.ts
│ │ ├── initializers
│ │ │ ├── google-analytics-router.initializer.spec.ts
│ │ │ ├── google-analytics-router.initializer.ts
│ │ │ └── google-analytics.initializer.ts
│ │ ├── interfaces
│ │ │ ├── i-google-analytics-command.ts
│ │ │ ├── i-google-analytics-routing-settings.ts
│ │ │ └── i-google-analytics-settings.ts
│ │ ├── ngx-google-analytics-router
│ │ │ └── ngx-google-analytics-router.module.ts
│ │ ├── ngx-google-analytics.module.ts
│ │ ├── services
│ │ │ ├── google-analytics.service.spec.ts
│ │ │ └── google-analytics.service.ts
│ │ ├── tokens
│ │ │ ├── ngx-data-layer-token.ts
│ │ │ ├── ngx-google-analytics-router-settings-token.ts
│ │ │ ├── ngx-google-analytics-settings-token.ts
│ │ │ ├── ngx-google-analytics-window.ts
│ │ │ ├── ngx-gtag-token.ts
│ │ │ └── ngx-window-token.ts
│ │ └── types
│ │ │ ├── data-layer.type.ts
│ │ │ ├── ga-action.type.ts
│ │ │ └── gtag.type.ts
│ ├── public_api.ts
│ └── test.ts
│ ├── tsconfig.lib.json
│ ├── tsconfig.lib.prod.json
│ └── tsconfig.spec.json
├── src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── test-page-a
│ │ ├── test-page-a.component.css
│ │ ├── test-page-a.component.html
│ │ ├── test-page-a.component.spec.ts
│ │ └── test-page-a.component.ts
│ └── test-page-b
│ │ ├── test-page-b.component.css
│ │ ├── test-page-b.component.html
│ │ ├── test-page-b.component.spec.ts
│ │ └── test-page-b.component.ts
├── assets
│ └── .gitkeep
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── karma.conf.js
├── main.ts
├── polyfills.ts
├── styles.css
├── test.ts
├── tsconfig.app.json
└── tsconfig.spec.json
└── tsconfig.json
/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 | #
5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
6 |
7 | > 0.5%
8 | last 2 versions
9 | Firefox ESR
10 | not dead
11 | not IE 9-11
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json",
14 | "e2e/tsconfig.json"
15 | ],
16 | "createDefaultProgram": true
17 | },
18 | "extends": [
19 | "plugin:@angular-eslint/recommended",
20 | "plugin:@angular-eslint/template/process-inline-templates"
21 | ],
22 | "rules": {
23 | "@angular-eslint/component-selector": [
24 | "error",
25 | {
26 | "prefix": "ga",
27 | "style": "kebab-case",
28 | "type": "element"
29 | }
30 | ],
31 | "@angular-eslint/directive-selector": [
32 | "error",
33 | {
34 | "prefix": "ga",
35 | "style": "camelCase",
36 | "type": "attribute"
37 | }
38 | ]
39 | }
40 | },
41 | {
42 | "files": [
43 | "*.html"
44 | ],
45 | "extends": [
46 | "plugin:@angular-eslint/template/recommended"
47 | ],
48 | "rules": {}
49 | }
50 | ]
51 | }
52 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
3 |
4 | name: Bump and Publish
5 |
6 | on:
7 | release:
8 | types: [released]
9 | # refs/tags/x.x.x
10 |
11 | jobs:
12 | tests:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - uses: actions/setup-node@v1
17 | with:
18 | node-version: 16
19 | - name: 'Cache node Modules'
20 | uses: actions/cache@v1
21 | with:
22 | path: ~.npm
23 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
24 | restore-keys: |
25 | ${{ runner.os }}-node-
26 | - run: npm ci --ignore-scripts
27 | - run: npm test
28 |
29 | bump-and-build:
30 | needs: tests
31 | runs-on: ubuntu-latest
32 | steps:
33 | - name: Get the version
34 | id: get_version
35 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
36 | - uses: actions/checkout@v2
37 | - uses: actions/setup-node@v1
38 | with:
39 | node-version: 16
40 | registry-url: https://registry.npmjs.org/
41 | - name: 'Cache node Modules'
42 | uses: actions/cache@v1
43 | with:
44 | path: ~.npm
45 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
46 | restore-keys: |
47 | ${{ runner.os }}-node-
48 | - run: npm ci --ignore-scripts
49 | - run: npm --no-git-tag-version --allow-same-version version ${{ steps.get_version.outputs.VERSION }}
50 | - run: npm --no-git-tag-version --allow-same-version version ${{ steps.get_version.outputs.VERSION }}
51 | working-directory: projects/ngx-google-analytics
52 | # - name: 'Commit Bump to ${{ steps.get_version.outputs.VERSION }}'
53 | # run: |
54 | # echo git config --local user.email "action@github.com"
55 | # echo git config --local user.name "GitHub Action"
56 | # git add package.json
57 | # git add projects/ngx-google-analytics/package.json
58 | # git status
59 | # git commit -m "Bump to ${{ steps.get_version.outputs.VERSION }} [skip ci]"
60 | # echo git push
61 | - run: npm run build
62 | - run: |
63 | cp LICENSE dist/ngx-google-analytics
64 | cp README.md dist/ngx-google-analytics
65 | cp CHANGELOG.md dist/ngx-google-analytics
66 | - run: npm pack
67 | working-directory: dist/ngx-google-analytics
68 | - name: 'Upload Build Artifatc ${{ steps.get_version.outputs.VERSION }}'
69 | uses: actions/upload-artifact@v2
70 | with:
71 | name: ${{ steps.get_version.outputs.VERSION }}.tgz
72 | path: dist/ngx-google-analytics/ngx-google-analytics-${{ steps.get_version.outputs.VERSION }}.tgz
73 |
74 | publish-npm:
75 | needs: bump-and-build
76 | runs-on: ubuntu-latest
77 | steps:
78 | - name: Get the version
79 | id: get_version
80 | run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
81 | - name: Download a Build Artifact
82 | uses: actions/download-artifact@v2
83 | with:
84 | name: ${{ steps.get_version.outputs.VERSION }}.tgz
85 | - uses: actions/setup-node@v1
86 | with:
87 | node-version: 16
88 | registry-url: https://registry.npmjs.org/
89 | - run: npm publish ngx-google-analytics-${{ steps.get_version.outputs.VERSION }}.tgz
90 | env:
91 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
92 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: Build and Tests
4 |
5 | # Controls when the action will run. Triggers the workflow on push or pull request
6 | # events but only for the master branch
7 | on:
8 | push:
9 | branches:
10 | - master
11 | - releases/*
12 | pull_request:
13 | branches:
14 | - master
15 | - releases/*
16 |
17 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
18 | jobs:
19 | run-tests:
20 | # The type of runner that the job will run on
21 | if: "!contains(github.event.head_commit.message, 'skip ci')"
22 | runs-on: ubuntu-latest
23 |
24 | steps:
25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
26 | - uses: actions/checkout@v2
27 | - uses: actions/setup-node@v1
28 | with:
29 | node-version: '16'
30 | registry-url: 'https://registry.npmjs.org'
31 |
32 | - name: 'Cache node Modules'
33 | uses: actions/cache@v1
34 | with:
35 | path: ~.npm
36 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
37 | restore-keys: |
38 | ${{ runner.os }}-node-
39 |
40 | - run: npm ci --ignore-scripts
41 | env:
42 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
43 |
44 | - name: Build Angular Library
45 | run: npm run build
46 |
47 | - name: Running Tests
48 | run: npm run test
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.angular/cache
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=https://registry.npmjs.org
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "tslint.autoFixOnSave": true,
3 | "editor.detectIndentation": false,
4 | "editor.tabSize": 2,
5 | "editor.codeActionsOnSave": {
6 | "source.fixAll.tslint": true
7 | }
8 | }
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | * [14.0.0](#14.0.0)
4 | * [13.0.1](#13.0.1)
5 | * [13.0.0](#13.0.0)
6 | * [12.0.0](#12.0.0)
7 | * [11.2.1](#11.2.1)
8 | * [11.2.0](#11.2.0)
9 | * [11.1.0](#11.1.0)
10 | * [11.0.0](#11.0.0)
11 | * [10.0.0](#10.0.0)
12 | * [9.2.0](#9.2.0)
13 | * [9.1.1](#9.1.1)
14 | * [9.1.0](#9.1.0)
15 | * [9.0.1](#9.0.1)
16 | * [9.0.0](#9.0.0)
17 | * [8.1.0](#8.1.0)
18 | * [8.0.0](#8.0.0)
19 |
20 | ## DISCLAIMER
21 |
22 | I open my heart to share this component w/ you guys, buy I don't have much free time to keep this project always up to date, so if you find a Bug or a freek behaviour, please, fell free to open de source code and submit a PR to help yourself and other guys that use this lib too. :)
23 |
24 | I will upgrade this pack to any angular major version as soon as possible. Unfortunately I can't replicate new features to old compatibility versions. But you can fork this repo and port does features.
25 |
26 | ## 14.0.0
27 | * Adding additional optional parameters to allow for more rob…
28 | * Update to support angular 14 (#96)
29 |
30 | ## 13.0.1
31 | * Bump Karma
32 | * Bump Jasmine
33 | * Bump RXJS to 7.4.0
34 | * Migrate from TSLint to ESLint
35 |
36 | ## 13.0.0
37 | * Bump to ng v13
38 |
39 | ## 12.0.0
40 | * Bump to ng v12
41 |
42 | ## 11.2.1
43 |
44 | * Allow override initial commands
45 |
46 | ## 11.2.0
47 |
48 | * Fixed parameter initCommands on NgxGoogleAnalyticsModule.forRoot() #46
49 | * Allow directive gaBind to trigger on any kind of event. #43
50 |
51 | ## 11.1.0
52 |
53 | * Using enum instead of string type (#38)
54 |
55 | ## 11.0.0
56 |
57 | * Bump to ng v11
58 |
59 | ## 10.0.0
60 |
61 | * Bump to ng v10
62 |
63 | ## 9.2.0
64 |
65 | * Add include/exclude rules feature on NgxGoogleAnalyticsRouterModule.forRoot() to filter witch pages should trigger page-view event.
66 | * Remove `peerDependencies` from package.json to do not trigger unnecessary warnings on `npm install` command.
67 |
68 | ## 9.1.1
69 |
70 | * [Bugfix] Set nonce using `setAttribute`
71 |
72 | ## 9.1.0
73 |
74 | * Add nonce
75 | * Fix typos
76 | * Rename i-google-analytics-command.ts
77 |
78 | ## 9.0.1
79 |
80 | * Created set() method at GoogleAnalyticsService (https://developers.google.com/analytics/devguides/collection/gtagjs/setting-values);
81 | * Changed gtag() method signature at GoogleAnalyticsService to acept anything;
82 | * Added a filter to remove undefined values to rest parameter on gtag() fn;
83 |
84 | ## 9.0.0
85 |
86 | Just bump to Angular ^9.x
87 |
88 | ## 8.1.0
89 |
90 | I finally get some time this weekend and decide to work on some unfinished issues. there it go:
91 |
92 | * Created and Updated unit tests on library project;
93 | * Created an automated workflow to run unit tests on each PR;
94 | * Created TypeDocs on all Services, Modules and Directives to help you guys to use this lib;
95 | * Removed bad practices on access Window and Document objects directily by Angular Services. I decided to create Injection Tokens to resolve does Broser Objects.;
96 | * Added some validations to ensure it is a Browser Environment;
97 | * Added cleanup code on NgxGoogleAnalyticsRouterModule. In short, we now unsubscribe Router events when bootstrap app is destroied;
98 | * Added a new Settings property `ennableTracing` to log on console Errors and Warnings about `gtag()` calls;
99 | * Now we have `InjectionToken` for everything. You can replace all our default settings;
100 |
101 | ## 8.0.0
102 |
103 | Sorry, I don't have time to catalog all changes done on the previous versions. You can get a detailed (😂) description of each previous versions on GitHub releases and commit histories. Don't worry, there are nothing still relevant there.
104 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Max
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ngx Google Analytics
2 |
3 | An easy implementation to track ga on angular8+ apps.
4 |
5 | Feedbacks on https://github.com/maxandriani/ngx-google-analytics
6 |
7 | 
8 |
9 | # Notice
10 |
11 | I'm investing a big amount of time studing new technologies for my daily job, and I am not able to invest a significant amount of time into maintaining `ngx-google-analytics` properly. I am looking for volunteers who would like to become active maintainers on the project. If you are interested, please shoot me a note.
12 |
13 | # Index
14 |
15 | * [Setup](#setup)
16 | * [NPM](#npm)
17 | * [Simple Setup](#simple-setup)
18 | * [Routing Setup](#setup-routing-module)
19 | * [Advanced Routing Setup](#advanced-setup-routing-module)
20 | * [GoogleAnalyticsService](#googleanalyticsservice)
21 | * [Directives](#directives)
22 | * [Changelog](CHANGELOG.md)
23 |
24 | ## Setup
25 |
26 | ### NPM
27 |
28 | To setup this package on you project, just call the following command.
29 |
30 | ```
31 | npm install ngx-google-analytics
32 | ```
33 |
34 | ### Simple Setup
35 |
36 | On your Angular Project, you shall include the `NgxGoogleAnalyticsModule` on your highest level application module. ie `AddModule`. The easiest install mode call the `forRoot()` method and pass the GA tracking code.
37 |
38 | ```ts
39 | import { NgxGoogleAnalyticsModule } from 'ngx-google-analytics';
40 |
41 | @NgModule({
42 | declarations: [
43 | AppComponent
44 | ],
45 | imports: [
46 | BrowserModule,
47 | NgxGoogleAnalyticsModule.forRoot('traking-code')
48 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
49 | ],
50 | providers: [],
51 | bootstrap: [AppComponent]
52 | })
53 | export class AppModule { }
54 | ```
55 |
56 | ### Setup Routing Module
57 |
58 | We provide a second Module Dependency to configure Router Event Bindings and perform automatic page view every time your application navigates to another page.
59 |
60 | Add ```NgxGoogleAnalyticsRouterModule``` on AppModule enable auto track `Router` events.
61 |
62 | **IMPORTANT:** This Module just subscribe to Router events when the bootstrap component is created, and then cleans up any subscriptions related to previous component when it is destroyed. You may get some issues if using this module on a server side rendering or multiple bootstrap components. If it is your case, I suggest you subscribe to events by yourself. You can use git repository as reference.
63 |
64 | ```ts
65 | import { NgxGoogleAnalyticsModule, NgxGoogleAnalyticsRouterModule } from 'ngx-google-analytics';
66 | ...
67 |
68 | @NgModule({
69 | ...
70 | imports: [
71 | ...
72 | NgxGoogleAnalyticsModule.forRoot(environment.ga),
73 | NgxGoogleAnalyticsRouterModule
74 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
75 | ]
76 | })
77 | export class AppModule {}
78 | ```
79 |
80 | ### Advanced Setup Routing Module
81 |
82 | You can customize some rules to include/exclude routes on `NgxGoogleAnalyticsRouterModule`. The include/exclude settings allow:
83 | * Simple route match: `{ include: [ '/full-uri-match' ] }`;
84 | * Wildcard route match: `{ include: [ '*/public/*' ] }`;
85 | * Regular Expression route match: `{ include: [ /^\/public\/.*/ ] }`;
86 |
87 | ```ts
88 | import { NgxGoogleAnalyticsModule, NgxGoogleAnalyticsRouterModule } from 'ngx-google-analytics';
89 | ...
90 |
91 | @NgModule({
92 | ...
93 | imports: [
94 | ...
95 | NgxGoogleAnalyticsModule.forRoot(environment.ga),
96 | NgxGoogleAnalyticsRouterModule.forRoot({ include: [...], exclude: [...] })
97 | // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
98 | ]
99 | })
100 | export class AppModule {}
101 | ```
102 |
103 |
104 | ## GoogleAnalyticsService
105 |
106 | This service provides an easy and strong typed way to call `gtag()` command. It does nothing else then convert a strong typed list of arguments into a standard `gtag` api call.
107 |
108 | ### Call Interface Events
109 |
110 | ```ts
111 | @Component( ... )
112 | export class TestFormComponent {
113 |
114 | constructor(
115 | private $gaService: GoogleAnalyticsService
116 | ) {}
117 |
118 | onUserInputName() {
119 | ...
120 | this.$gaService.event('enter_name', 'user_register_form', 'Name');
121 | }
122 |
123 | onUserInputEmail() {
124 | ...
125 | this.$gaService.event('enter_email', 'user_register_form', 'Email');
126 | }
127 |
128 | onSubmit() {
129 | ...
130 | this.$gaService.event('submit', 'user_register_form', 'Enviar');
131 | }
132 |
133 | }
134 | ```
135 |
136 | ### Call GA Page Views and Virtual Page Views
137 |
138 | ```ts
139 | @Component(...)
140 | export class TestPageComponent implements OnInit {
141 |
142 | constructor(
143 | protected $gaService: GoogleAnalyticsService
144 | ) {}
145 |
146 | ngOnInit() {
147 | this.$gaService.pageView('/teste', 'Teste de Title')
148 | }
149 |
150 | onUserLogin() {
151 | ...
152 | this.$gaService.pageView('/teste', 'Teste de Title', undefined, {
153 | user_id: 'my-user-id'
154 | })
155 | }
156 |
157 | }
158 | ```
159 |
160 | ## Directives
161 |
162 | In a way to help you to be more productive on attach GA events on UI elements. We create some directives to handle `GoogleAnalyticsService` and add event listener by simple attributes.
163 |
164 | ### Simple directive use
165 |
166 | The default behaviour is call `gtag` on click events, but you can change the trigger to any HTML Event (e.g. `focus`, `blur` or custom events) as well.
167 |
168 | ```js
169 |
170 | Click Test
171 | Focus Test
172 | Blur Test
173 | Custom Event Test
174 |
175 | ```
176 |
177 | ### Simple input use
178 |
179 | If you attach gaEvent directive on form elements, it will assume focus event as default `trigger`.
180 |
181 | ```js
182 |
183 |
184 |
185 | ```
186 |
187 | ### Grouped directives
188 |
189 | Sometimes your UX guy want to group several elements in the interface at same group to help his analysis and reports. Fortunately the `gaCategory` directive can be placed on the highest level group
190 | element and all child `gaEvent` will assume the parent `gaCategory` as their parent.
191 |
192 | ```js
193 |
194 | Click Test
195 | Focus Test
196 | Blur Test
197 |
198 | ```
199 |
200 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ngx-google-analytics-sdk": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "outputPath": "dist/ngx-google-analytics-sdk",
17 | "index": "src/index.html",
18 | "main": "src/main.ts",
19 | "polyfills": "src/polyfills.ts",
20 | "tsConfig": "src/tsconfig.app.json",
21 | "assets": [
22 | "src/favicon.ico",
23 | "src/assets"
24 | ],
25 | "styles": [
26 | "src/styles.css"
27 | ],
28 | "scripts": [],
29 | "vendorChunk": true,
30 | "extractLicenses": false,
31 | "buildOptimizer": false,
32 | "sourceMap": true,
33 | "optimization": false,
34 | "namedChunks": true
35 | },
36 | "configurations": {
37 | "production": {
38 | "budgets": [
39 | {
40 | "type": "anyComponentStyle",
41 | "maximumWarning": "6kb"
42 | }
43 | ],
44 | "fileReplacements": [
45 | {
46 | "replace": "src/environments/environment.ts",
47 | "with": "src/environments/environment.prod.ts"
48 | }
49 | ],
50 | "optimization": true,
51 | "outputHashing": "all",
52 | "sourceMap": false,
53 | "namedChunks": false,
54 | "extractLicenses": true,
55 | "vendorChunk": false,
56 | "buildOptimizer": true
57 | },
58 | "development": {}
59 | },
60 | "defaultConfiguration": "production"
61 | },
62 | "serve": {
63 | "builder": "@angular-devkit/build-angular:dev-server",
64 | "options": {},
65 | "configurations": {
66 | "production": {
67 | "browserTarget": "ngx-google-analytics-sdk:build:production"
68 | },
69 | "development": {
70 | "browserTarget": "ngx-google-analytics-sdk:build:development"
71 | }
72 | },
73 | "defaultConfiguration": "development"
74 | },
75 | "extract-i18n": {
76 | "builder": "@angular-devkit/build-angular:extract-i18n",
77 | "options": {
78 | "browserTarget": "ngx-google-analytics-sdk:build"
79 | }
80 | },
81 | "test": {
82 | "builder": "@angular-devkit/build-angular:karma",
83 | "options": {
84 | "main": "src/test.ts",
85 | "polyfills": "src/polyfills.ts",
86 | "tsConfig": "src/tsconfig.spec.json",
87 | "karmaConfig": "src/karma.conf.js",
88 | "styles": [
89 | "src/styles.css"
90 | ],
91 | "scripts": [],
92 | "assets": [
93 | "src/favicon.ico",
94 | "src/assets"
95 | ]
96 | }
97 | }
98 | }
99 | },
100 | "ngx-google-analytics-sdk-e2e": {
101 | "root": "e2e/",
102 | "projectType": "application",
103 | "architect": {
104 | "e2e": {
105 | "builder": "@angular-devkit/build-angular:protractor",
106 | "options": {
107 | "protractorConfig": "e2e/protractor.conf.js"
108 | },
109 | "configurations": {
110 | "production": {
111 | "devServerTarget": "ngx-google-analytics-sdk:serve:production"
112 | },
113 | "development": {
114 | "devServerTarget": "ngx-google-analytics-sdk:serve:development"
115 | }
116 | },
117 | "defaultConfiguration": "development"
118 | },
119 | "lint": {
120 | "builder": "@angular-eslint/builder:lint",
121 | "options": {
122 | "lintFilePatterns": [
123 | "src/**/*.ts",
124 | "src/**/*.html"
125 | ],
126 | "tsConfig": "e2e/tsconfig.e2e.json",
127 | "exclude": [
128 | "**/node_modules/**"
129 | ]
130 | }
131 | }
132 | }
133 | },
134 | "ngx-google-analytics": {
135 | "root": "projects/ngx-google-analytics",
136 | "sourceRoot": "projects/ngx-google-analytics/src",
137 | "projectType": "library",
138 | "prefix": "ga",
139 | "architect": {
140 | "build": {
141 | "builder": "@angular-devkit/build-angular:ng-packagr",
142 | "options": {
143 | "tsConfig": "projects/ngx-google-analytics/tsconfig.lib.json",
144 | "project": "projects/ngx-google-analytics/ng-package.json"
145 | },
146 | "configurations": {
147 | "production": {
148 | "tsConfig": "projects/ngx-google-analytics/tsconfig.lib.prod.json"
149 | },
150 | "development": {}
151 | },
152 | "defaultConfiguration": "production"
153 | },
154 | "test": {
155 | "builder": "@angular-devkit/build-angular:karma",
156 | "options": {
157 | "main": "projects/ngx-google-analytics/src/test.ts",
158 | "tsConfig": "projects/ngx-google-analytics/tsconfig.spec.json",
159 | "karmaConfig": "projects/ngx-google-analytics/karma.conf.js"
160 | }
161 | },
162 | "lint": {
163 | "builder": "@angular-eslint/builder:lint",
164 | "options": {
165 | "lintFilePatterns": [
166 | "src/**/*.ts",
167 | "src/**/*.html"
168 | ]
169 | }
170 | }
171 | }
172 | }
173 | },
174 | "defaultProject": "ngx-google-analytics-sdk",
175 | "cli": {
176 | "analytics": "3ce39043-c695-45ae-9eeb-07fed5fb3a2e",
177 | "defaultCollection": "@angular-eslint/schematics"
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/e2e/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 | const {StacktraceOption} = require("jasmine-spec-reporter/built/configuration");
6 |
7 | exports.config = {
8 | allScriptsTimeout: 11000,
9 | specs: [
10 | './src/**/*.e2e-spec.ts'
11 | ],
12 | capabilities: {
13 | 'browserName': 'chrome'
14 | },
15 | directConnect: true,
16 | baseUrl: 'http://localhost:4200/',
17 | framework: 'jasmine',
18 | jasmineNodeOpts: {
19 | showColors: true,
20 | defaultTimeoutInterval: 30000,
21 | print: function() {}
22 | },
23 | onPrepare() {
24 | require('ts-node').register({
25 | project: require('path').join(__dirname, './tsconfig.e2e.json')
26 | });
27 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: StacktraceOption.PRETTY } }));
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('workspace-project App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to ngx-google-analytics-sdk!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/src/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "module": "commonjs",
6 | "target": "es5",
7 | "types": [
8 | "jasmine",
9 | "jasminewd2",
10 | "node"
11 | ]
12 | }
13 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-google-analytics-sdk",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "ng": "ng",
6 | "prestart": "npm run build",
7 | "start": "ng serve",
8 | "build": "ng build ngx-google-analytics",
9 | "test": "ng test ngx-google-analytics --browsers=ChromeHeadless --watch=false",
10 | "test:dev": "ng test ngx-google-analytics",
11 | "lint": "ng lint ngx-google-analytics",
12 | "e2e": "ng e2e"
13 | },
14 | "private": true,
15 | "dependencies": {
16 | "@angular/animations": "^14.0.2",
17 | "@angular/cli": "~14.0.2",
18 | "@angular/common": "^14.0.2",
19 | "@angular/compiler": "^14.0.2",
20 | "@angular/core": "^14.0.2",
21 | "@angular/forms": "^14.0.2",
22 | "@angular/platform-browser": "^14.0.2",
23 | "@angular/platform-browser-dynamic": "^14.0.2",
24 | "@angular/router": "^14.0.2",
25 | "rxjs": "~7.5.5",
26 | "tslib": "^2.4.0",
27 | "zone.js": "~0.11.6"
28 | },
29 | "devDependencies": {
30 | "@angular-devkit/build-angular": "~14.0.2",
31 | "@angular-devkit/core": "^14.0.2",
32 | "@angular-devkit/schematics": "^14.0.2",
33 | "@angular-eslint/builder": "^14.0.0-alpha.3",
34 | "@angular-eslint/eslint-plugin": "^14.0.0-alpha.3",
35 | "@angular-eslint/eslint-plugin-template": "^14.0.0-alpha.3",
36 | "@angular-eslint/schematics": "^14.0.0-alpha.3",
37 | "@angular-eslint/template-parser": "^14.0.0-alpha.3",
38 | "@angular/compiler-cli": "^14.0.2",
39 | "@angular/language-service": "^14.0.2",
40 | "@types/jasmine": "~4.0.3",
41 | "@types/jasminewd2": "~2.0.10",
42 | "@types/node": "^17.0.44",
43 | "@typescript-eslint/eslint-plugin": "5.28.0",
44 | "@typescript-eslint/parser": "5.28.0",
45 | "codelyzer": "^6.0.2",
46 | "eslint": "^8.17.0",
47 | "jasmine-core": "~4.2.0",
48 | "jasmine-spec-reporter": "~7.0.0",
49 | "karma": "~6.4.0",
50 | "karma-chrome-launcher": "~3.1.1",
51 | "karma-coverage-istanbul-reporter": "~3.0.3",
52 | "karma-jasmine": "~5.0.1",
53 | "karma-jasmine-html-reporter": "^2.0.0",
54 | "ng-packagr": "^14.0.2",
55 | "protractor": "~7.0.0",
56 | "ts-node": "~10.8.1",
57 | "tslint": "~6.1.3",
58 | "typescript": "~4.7.3"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../../coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3 | "dest": "../../dist/ngx-google-analytics",
4 | "lib": {
5 | "entryFile": "src/public_api.ts"
6 | }
7 | }
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ngx-google-analytics",
3 | "version": "14.0.0",
4 | "description": "A simple ng-9 wrapper to load Google Analytics dependency by angular way",
5 | "keywords": [ "google", "google analytics", "angular", "angular-9+", "typescript" ],
6 | "homepage": "https://github.com/maxandriani/ngx-google-analytics",
7 | "license": "MIT",
8 | "dependencies": {
9 | "tslib": "^2.4.0"
10 | },
11 | "peerDependencies": {
12 | "@angular/common": ">=12.0.0",
13 | "@angular/core": ">=12.0.0"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/maxandriani/ngx-google-analytics"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Fix the anoing problem when you try to use this library on the sabe enviroment and VSCode always user public_api path to import files.
3 | */
4 | export * from './public_api';
5 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/directives/ga-event-category.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { GaEventCategoryDirective } from './ga-event-category.directive';
2 |
3 | describe('GaEventCategoryDirective', () => {
4 | it('should create an instance', () => {
5 | const directive = new GaEventCategoryDirective();
6 | expect(directive).toBeTruthy();
7 | });
8 | });
9 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/directives/ga-event-category.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, Input } from '@angular/core';
2 |
3 | @Directive({
4 | selector: `[gaEvent][gaCategory],
5 | [gaCategory]`,
6 | exportAs: 'gaCategory'
7 | })
8 | export class GaEventCategoryDirective {
9 |
10 | constructor() { }
11 |
12 | @Input() gaCategory!: string;
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/directives/ga-event-form-input.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { GaEventFormInputDirective } from './ga-event-form-input.directive';
2 | import { TestBed, ComponentFixture } from '@angular/core/testing';
3 | import { NgxGoogleAnalyticsModule } from '../ngx-google-analytics.module';
4 | import { GaEventDirective } from './ga-event.directive';
5 | import { GaEventCategoryDirective } from './ga-event-category.directive';
6 | import { GoogleAnalyticsService } from '../services/google-analytics.service';
7 | import { Component } from '@angular/core';
8 | import { NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN } from '../tokens/ngx-google-analytics-settings-token';
9 |
10 | describe('GaEventFormInputDirective', () => {
11 |
12 | @Component({
13 | selector: 'ga-host',
14 | template: ` `
15 | })
16 | class HostComponent {}
17 |
18 | let gaEventFormInput: GaEventFormInputDirective,
19 | gaEvent: GaEventDirective,
20 | gaCategory: GaEventCategoryDirective,
21 | host: HostComponent,
22 | fixture: ComponentFixture;
23 |
24 | beforeEach(() => {
25 | TestBed.configureTestingModule({
26 | imports: [
27 | NgxGoogleAnalyticsModule
28 | ],
29 | declarations: [
30 | HostComponent
31 | ]
32 | }).compileComponents();
33 | });
34 |
35 | beforeEach(() => {
36 | fixture = TestBed.createComponent(HostComponent);
37 | host = fixture.componentInstance;
38 | fixture.detectChanges();
39 | });
40 |
41 | beforeEach(() => {
42 | gaCategory = new GaEventCategoryDirective();
43 | gaEvent = new GaEventDirective(gaCategory, TestBed.inject(GoogleAnalyticsService), TestBed.inject(NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN), fixture.elementRef);
44 | gaEventFormInput = new GaEventFormInputDirective(gaEvent);
45 | });
46 |
47 | it('should create an instance', () => {
48 | expect(gaEventFormInput).toBeTruthy();
49 | });
50 |
51 | it('should update gaBind when input is updated', () => {
52 | gaEventFormInput.gaBind = 'click';
53 | expect(gaEvent.gaBind).toBe('click');
54 | });
55 |
56 | it('should use `focus` as a default gaBind', () => {
57 | expect(gaEvent.gaBind).toBe('focus');
58 | });
59 |
60 | it('should call `GoogleAnalyticsService.event()` on trigger focus at input', () => {
61 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
62 | spyOnGa = spyOn(ga, 'event'),
63 | input = fixture.debugElement.query(e => e.name === 'input');
64 |
65 | fixture.detectChanges();
66 | input.nativeElement.dispatchEvent(new FocusEvent('focus'));
67 | fixture.detectChanges();
68 |
69 | expect(spyOnGa).toHaveBeenCalledWith('teste', undefined, undefined, undefined, undefined);
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/directives/ga-event-form-input.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, Host, Optional, Input } from '@angular/core';
2 | import { GaEventDirective } from './ga-event.directive';
3 |
4 | @Directive({
5 | selector: `input[gaEvent],
6 | select[gaEvent],
7 | textarea[gaEvent]`
8 | })
9 | export class GaEventFormInputDirective {
10 |
11 | constructor(
12 | @Host() @Optional() protected gaEvent: GaEventDirective
13 | ) {
14 | this.gaBind = 'focus';
15 | }
16 |
17 | @Input() set gaBind(bind: string) {
18 | if (this.gaEvent) {
19 | this.gaEvent.gaBind = bind;
20 | }
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/directives/ga-event.directive.spec.ts:
--------------------------------------------------------------------------------
1 | import { GaEventDirective } from './ga-event.directive';
2 | import { TestBed, ComponentFixture } from '@angular/core/testing';
3 | import { NgxGoogleAnalyticsModule } from '../ngx-google-analytics.module';
4 | import { GoogleAnalyticsService } from '../services/google-analytics.service';
5 | import { Component } from '@angular/core';
6 | import { GaActionEnum } from '../enums/ga-action.enum';
7 |
8 | describe('GaEventDirective', () => {
9 |
10 | @Component({
11 | selector: 'ga-host',
12 | template: `
13 |
21 |
30 |
39 |
48 |
57 | `
58 | })
59 | class HostComponent {
60 | gaAction: GaActionEnum | string;
61 | gaLabel: string;
62 | label: string;
63 | gaValue: number;
64 | gaInteraction: boolean;
65 | gaBind = 'click';
66 | gaEvent: GaActionEnum | string;
67 | }
68 |
69 | let fixture: ComponentFixture,
70 | host: HostComponent;
71 |
72 | beforeEach(() => {
73 | TestBed.configureTestingModule({
74 | imports: [
75 | NgxGoogleAnalyticsModule
76 | ],
77 | declarations: [
78 | HostComponent
79 | ]
80 | }).compileComponents();
81 | });
82 |
83 | beforeEach(() => {
84 | fixture = TestBed.createComponent(HostComponent);
85 | host = fixture.componentInstance;
86 | // gaCategory = fixture
87 | // .debugElement
88 | // .query(c => c.classes['test-4'])
89 | // .injector
90 | // .get(GaEventCategoryDirective);
91 | // gaEvent = new GaEventDirective(gaCategory, TestBed.inject(GoogleAnalyticsService));
92 | });
93 |
94 | it('should create an instance', () => {
95 | const gaEvent = fixture
96 | .debugElement
97 | .query(i => (i.nativeElement as HTMLButtonElement).classList.contains('test-1'))
98 | .injector
99 | .get(GaEventDirective);
100 | expect(gaEvent).toBeTruthy();
101 | });
102 |
103 | it('should call `trigger` on click event', () => {
104 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
105 | spyOnGa = spyOn(ga, 'event'),
106 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-click'));
107 |
108 | fixture.detectChanges();
109 | input.nativeElement.click();
110 | fixture.detectChanges();
111 |
112 | expect(spyOnGa).toHaveBeenCalledWith('test-1', undefined, undefined, undefined, undefined);
113 | });
114 |
115 | it('should call `trigger` on focus event', () => {
116 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
117 | spyOnGa = spyOn(ga, 'event'),
118 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-focus'));
119 |
120 | fixture.detectChanges();
121 | input.nativeElement.dispatchEvent(new FocusEvent('focus'));
122 | fixture.detectChanges();
123 |
124 | expect(spyOnGa).toHaveBeenCalledWith('test-2', undefined, undefined, undefined, undefined);
125 | });
126 |
127 | it('should call `trigger on blur event`', () => {
128 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
129 | spyOnGa = spyOn(ga, 'event'),
130 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-blur'));
131 |
132 | fixture.detectChanges();
133 | input.nativeElement.dispatchEvent(new FocusEvent('blur'));
134 | fixture.detectChanges();
135 |
136 | expect(spyOnGa).toHaveBeenCalledWith('test-3', undefined, undefined, undefined, undefined);
137 | });
138 |
139 | it('should call `trigger on custom event`', () => {
140 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
141 | spyOnGa = spyOn(ga, 'event'),
142 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-custom'));
143 |
144 | fixture.detectChanges();
145 | input.nativeElement.dispatchEvent(new CustomEvent('custom'));
146 | fixture.detectChanges();
147 |
148 | expect(spyOnGa).toHaveBeenCalledWith('test-5', undefined, undefined, undefined, undefined);
149 | });
150 |
151 | it('should warn a message when try to call a event w/o gaEvent/gaAction value', () => {
152 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
153 | spyOnConsole = spyOn(console, 'warn'),
154 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
155 |
156 | fixture.detectChanges();
157 | input.nativeElement.click();
158 | fixture.detectChanges();
159 |
160 | expect(spyOnConsole).toHaveBeenCalled();
161 | });
162 |
163 | it('should grab gaAction and pass to event trigger', () => {
164 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
165 | action = 'action-t',
166 | spyOnGa = spyOn(ga, 'event'),
167 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
168 |
169 | host.gaAction = action;
170 | fixture.detectChanges();
171 | input.nativeElement.click();
172 | fixture.detectChanges();
173 |
174 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', undefined, undefined, undefined);
175 | });
176 |
177 | it('should grab gaEvent and pass to event trigger', () => {
178 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
179 | action = 'action-t',
180 | spyOnGa = spyOn(ga, 'event'),
181 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
182 |
183 | host.gaEvent = action;
184 | fixture.detectChanges();
185 | input.nativeElement.click();
186 | fixture.detectChanges();
187 |
188 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', undefined, undefined, undefined);
189 | });
190 |
191 | it('should grab gaCategory and pass to event trigger', () => {
192 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
193 | action = 'action-t',
194 | spyOnGa = spyOn(ga, 'event'),
195 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
196 |
197 | host.gaAction = action;
198 | fixture.detectChanges();
199 | input.nativeElement.click();
200 | fixture.detectChanges();
201 |
202 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', undefined, undefined, undefined);
203 | });
204 |
205 | it('should grab gaLabel and pass to event trigger', () => {
206 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
207 | action = 'action-t',
208 | label = 'label-t',
209 | spyOnGa = spyOn(ga, 'event'),
210 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
211 |
212 | host.gaAction = action;
213 | host.gaLabel = label;
214 | fixture.detectChanges();
215 | input.nativeElement.click();
216 | fixture.detectChanges();
217 |
218 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', label, undefined, undefined);
219 | });
220 |
221 | it('should grab label and pass to event trigger', () => {
222 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
223 | action = 'action-t',
224 | label = 'label-t',
225 | spyOnGa = spyOn(ga, 'event'),
226 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
227 |
228 | host.gaAction = action;
229 | host.label = label;
230 | fixture.detectChanges();
231 | input.nativeElement.click();
232 | fixture.detectChanges();
233 |
234 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', label, undefined, undefined);
235 | });
236 |
237 | it('should grab gaValue and pass to event trigger', () => {
238 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
239 | action = 'action-t',
240 | value = 40,
241 | spyOnGa = spyOn(ga, 'event'),
242 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
243 |
244 | host.gaAction = action;
245 | host.gaValue = value;
246 | fixture.detectChanges();
247 | input.nativeElement.click();
248 | fixture.detectChanges();
249 |
250 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', undefined, value, undefined);
251 | });
252 |
253 | it('should grab gaInteraction and pass to event trigger', () => {
254 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService),
255 | action = 'action-t',
256 | gaInteraction = true,
257 | spyOnGa = spyOn(ga, 'event'),
258 | input = fixture.debugElement.query(e => (e.nativeElement as HTMLButtonElement).classList.contains('test-category'));
259 |
260 | host.gaAction = action;
261 | host.gaInteraction = gaInteraction;
262 | fixture.detectChanges();
263 | input.nativeElement.click();
264 | fixture.detectChanges();
265 |
266 | expect(spyOnGa).toHaveBeenCalledWith(action, 'test-4', undefined, undefined, gaInteraction);
267 | });
268 |
269 | });
270 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/directives/ga-event.directive.ts:
--------------------------------------------------------------------------------
1 | import {Directive, ElementRef, Inject, Input, isDevMode, OnDestroy, Optional} from '@angular/core';
2 | import {GaEventCategoryDirective} from './ga-event-category.directive';
3 | import {GoogleAnalyticsService} from '../services/google-analytics.service';
4 | import {GaActionEnum} from '../enums/ga-action.enum';
5 | import {NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN} from '../tokens/ngx-google-analytics-settings-token';
6 | import {IGoogleAnalyticsSettings} from '../interfaces/i-google-analytics-settings';
7 | import {fromEvent, Subscription} from 'rxjs';
8 |
9 | @Directive({
10 | selector: `[gaEvent]`,
11 | exportAs: 'gaEvent'
12 | })
13 | export class GaEventDirective implements OnDestroy {
14 |
15 | constructor(
16 | @Optional() private gaCategoryDirective: GaEventCategoryDirective,
17 | private gaService: GoogleAnalyticsService,
18 | @Inject(NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN) private settings: IGoogleAnalyticsSettings,
19 | private readonly el: ElementRef
20 | ) {
21 | this.gaBind = 'click';
22 | }
23 |
24 | private bindSubscription?: Subscription;
25 |
26 | @Input() gaAction!: GaActionEnum | string;
27 | @Input() gaLabel!: string;
28 | @Input() label!: string;
29 | @Input() gaValue!: number;
30 | @Input() gaInteraction!: boolean;
31 | @Input() gaEvent!: GaActionEnum | string;
32 |
33 | private _gaBind!: string;
34 |
35 | @Input() set gaBind (gaBind: string) {
36 | if (this.bindSubscription) {
37 | this.bindSubscription.unsubscribe();
38 | }
39 |
40 | this._gaBind = gaBind;
41 | this.bindSubscription = fromEvent(this.el.nativeElement, gaBind).subscribe(() => this.trigger());
42 | }
43 | get gaBind(): string {
44 | return this._gaBind;
45 | }
46 |
47 | ngOnDestroy() {
48 | if (this.bindSubscription) {
49 | this.bindSubscription.unsubscribe();
50 | }
51 | }
52 |
53 | protected trigger() {
54 | try {
55 | // Observação: não é obrigatório especificar uma categoria, uma etiqueta ou um valor. Consulte Eventos padrão do Google Analytics abaixo.
56 | // if (!this.$gaCategoryDirective) {
57 | // throw new Error('You must provide a gaCategory attribute w/ gaEvent Directive.');
58 | // }
59 |
60 | if (!this.gaAction && !this.gaEvent) {
61 | throw new Error('You must provide a gaAction attribute to identify this event.');
62 | }
63 |
64 | this.gaService
65 | .event(
66 | this.gaAction || this.gaEvent,
67 | (this.gaCategoryDirective) ? this.gaCategoryDirective.gaCategory : undefined,
68 | this.gaLabel || this.label,
69 | this.gaValue,
70 | this.gaInteraction
71 | );
72 | } catch (err: any) {
73 | this.throw(err);
74 | }
75 | }
76 |
77 | protected throw(err: Error) {
78 | if ((isDevMode() || this.settings.enableTracing) && console && console.warn) {
79 | console.warn(err);
80 | }
81 | }
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/enums/ga-action.enum.ts:
--------------------------------------------------------------------------------
1 | export enum GaActionEnum {
2 | ADD_PAYMENT_INFO = 'add_payment_info',
3 | ADD_TO_CART = 'add_to_cart',
4 | ADD_TO_WISHLIST = 'add_to_wishlist',
5 | BEGIN_CHECKOUT = 'begin_checkout',
6 | CHECKOUT_PROGRESS = 'checkout_progress',
7 | GENERATE_LEAD = 'generate_lead',
8 | LOGIN = 'login',
9 | PURCHASE = 'purchase',
10 | REFUND = 'refund',
11 | REMOVE_FROM_CART = 'remove_from_cart',
12 | SEARCH = 'search',
13 | SELECT_CONTENT = 'select_content',
14 | SET_CHECKOUT_OPTION = 'set_checkout_option',
15 | SHARE = 'share',
16 | SIGN_UP = 'sign_up',
17 | VIEW_ITEM = 'view_item',
18 | VIEW_ITEM_LIST = 'view_item_list',
19 | VIEW_PROMOTION = 'view_promotion',
20 | VIEW_SEARCH_RESULT = 'view_search_results',
21 | VIEW_SEARCH_RESULTS = 'view_search_results',
22 | }
23 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/initializers/google-analytics-router.initializer.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentRef, Injector } from '@angular/core';
2 | import { TestBed } from '@angular/core/testing';
3 | import { NavigationEnd, NavigationStart, Router } from '@angular/router';
4 | import { Subject } from 'rxjs';
5 | import { GoogleAnalyticsService } from '../services/google-analytics.service';
6 | import { GoogleAnalyticsRouterInitializer } from './google-analytics-router.initializer';
7 |
8 | describe('googleAnalyticsRouterInitializer(settings, gaService)', () => {
9 |
10 | function fakeTransform(obj: Partial): Dest {
11 | return obj as any;
12 | }
13 |
14 | let gaService: GoogleAnalyticsService,
15 | spyOnGaService: jasmine.Spy,
16 | router$: Subject,
17 | router: Router,
18 | component: ComponentRef;
19 |
20 | beforeEach(() => {
21 | gaService = TestBed.inject(GoogleAnalyticsService);
22 | spyOnGaService = spyOn(gaService, 'pageView');
23 | router$ = new Subject();
24 | router = fakeTransform({
25 | events: router$
26 | });
27 | component = fakeTransform>({
28 | injector: fakeTransform({
29 | get: () => router
30 | }),
31 | onDestroy: () => {}
32 | });
33 | });
34 |
35 | it('should not trigger the first route event', async () => {
36 | const factory = await GoogleAnalyticsRouterInitializer(null, gaService)(component);
37 |
38 | // act
39 | router$.next(new NavigationStart(1, '/test'));
40 | router$.next(new NavigationEnd(1, '/test', '/test'));
41 | router$.next(new NavigationEnd(1, '/test', '/test'));
42 |
43 | // asserts
44 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
45 | });
46 |
47 | it('should trigger the second route event', async () => {
48 | const factory = await GoogleAnalyticsRouterInitializer(null, gaService)(component);
49 |
50 | // act
51 | router$.next(new NavigationStart(1, '/test'));
52 | router$.next(new NavigationEnd(1, '/test', '/test'));
53 | router$.next(new NavigationEnd(1, '/test', '/test'));
54 |
55 | // asserts
56 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
57 | expect(spyOnGaService).toHaveBeenCalledWith('/test', undefined);
58 | });
59 |
60 | it('should trigger only included route', async () => {
61 | const factory = await GoogleAnalyticsRouterInitializer({ include: [ '/test' ] }, gaService)(component);
62 |
63 | // act
64 | router$.next(new NavigationStart(1, '/test'));
65 | router$.next(new NavigationEnd(1, '/test', '/test'));
66 | router$.next(new NavigationEnd(1, '/test', '/test'));
67 | router$.next(new NavigationStart(1, '/test1'));
68 | router$.next(new NavigationEnd(1, '/test1', '/test1'));
69 | router$.next(new NavigationStart(1, '/test2'));
70 | router$.next(new NavigationEnd(1, '/test2', '/test2'));
71 |
72 | // asserts
73 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
74 | expect(spyOnGaService).toHaveBeenCalledWith('/test', undefined);
75 | });
76 |
77 | it('should not trigger excluded route', async () => {
78 | const factory = await GoogleAnalyticsRouterInitializer({ exclude: [ '/test' ] }, gaService)(component);
79 |
80 | // act
81 | router$.next(new NavigationStart(1, '/test1'));
82 | router$.next(new NavigationEnd(1, '/test1', '/test1'));
83 | router$.next(new NavigationStart(1, '/test2'));
84 | router$.next(new NavigationEnd(1, '/test2', '/test2'));
85 | router$.next(new NavigationStart(1, '/test'));
86 | router$.next(new NavigationEnd(1, '/test', '/test'));
87 | router$.next(new NavigationEnd(1, '/test', '/test'));
88 |
89 | // asserts
90 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
91 | expect(spyOnGaService).toHaveBeenCalledWith('/test2', undefined);
92 | });
93 |
94 | it('should work w/ include and exclude router', async () => {
95 | const factory = await GoogleAnalyticsRouterInitializer({
96 | include: [ '/test*' ],
97 | exclude: [ '/test-2' ] },
98 | gaService
99 | )(component);
100 |
101 | // act
102 | router$.next(new NavigationStart(1, '/test-1'));
103 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
104 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
105 | router$.next(new NavigationStart(1, '/test-2'));
106 | router$.next(new NavigationEnd(1, '/test-2', '/test-2'));
107 |
108 | // asserts
109 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
110 | expect(spyOnGaService).toHaveBeenCalledWith('/test-1', undefined);
111 | });
112 |
113 | it('should match simple uri', async () => {
114 | const factory = await GoogleAnalyticsRouterInitializer({
115 | include: [ '/test-1' ] },
116 | gaService
117 | )(component);
118 |
119 | // act
120 | router$.next(new NavigationStart(1, '/test-1'));
121 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
122 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
123 |
124 | // asserts
125 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
126 | expect(spyOnGaService).toHaveBeenCalledWith('/test-1', undefined);
127 | });
128 |
129 | it('should match wildcard uri', async () => {
130 | const factory = await GoogleAnalyticsRouterInitializer(
131 | {
132 | include: [ '/test*' ]
133 | },
134 | gaService
135 | )(component);
136 |
137 | // act
138 | router$.next(new NavigationStart(1, '/test-1'));
139 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
140 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
141 |
142 | // asserts
143 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
144 | expect(spyOnGaService).toHaveBeenCalledWith('/test-1', undefined);
145 | });
146 |
147 | it('should match RegExp uri', async () => {
148 | const factory = await GoogleAnalyticsRouterInitializer({
149 | include: [ new RegExp('/test.*', 'i') ] },
150 | gaService
151 | )(component);
152 |
153 | // act
154 | router$.next(new NavigationStart(1, '/test-1'));
155 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
156 | router$.next(new NavigationEnd(1, '/test-1', '/test-1'));
157 |
158 | // asserts
159 | expect(spyOnGaService).toHaveBeenCalledTimes(1);
160 | expect(spyOnGaService).toHaveBeenCalledWith('/test-1', undefined);
161 | });
162 |
163 | });
164 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/initializers/google-analytics-router.initializer.ts:
--------------------------------------------------------------------------------
1 | import { Provider, APP_BOOTSTRAP_LISTENER, ComponentRef } from '@angular/core';
2 | import { Router, NavigationEnd } from '@angular/router';
3 | import { IGoogleAnalyticsRoutingSettings } from '../interfaces/i-google-analytics-routing-settings';
4 | import { GoogleAnalyticsService } from '../services/google-analytics.service';
5 | import { NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN } from '../tokens/ngx-google-analytics-router-settings-token';
6 | import { filter, skip } from 'rxjs/operators';
7 |
8 | /**
9 | * Provide a DI Configuration to attach GA Trigger to Router Events at Angular Startup Cycle.
10 | */
11 | export const NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER: Provider = {
12 | provide: APP_BOOTSTRAP_LISTENER,
13 | multi: true,
14 | useFactory: GoogleAnalyticsRouterInitializer,
15 | deps: [
16 | NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN,
17 | GoogleAnalyticsService
18 | ]
19 | };
20 |
21 | /**
22 | * Attach a listener to `NavigationEnd` Router event. So, every time Router finish the page resolution it should call `NavigationEnd` event.
23 | * We assume that NavigationEnd is the final page resolution and call GA `page_view` command.
24 | *
25 | * To avoid double binds, we also destroy the subscription when de Bootstrap Component is destroied. But, we don't know for sure
26 | * that this strategy does not cause double bind on multiple bootstrap components.
27 | *
28 | * We are using de component's injector reference to resolve Router, sou I hope there is no problem w/ double bing.
29 | *
30 | * If you have this problem, I encourage not Use NgxGoogleAnalyticsRouterModule and atach the listener on AppComponent initialization.
31 | */
32 | export function GoogleAnalyticsRouterInitializer(
33 | settings: IGoogleAnalyticsRoutingSettings,
34 | gaService: GoogleAnalyticsService
35 | ) {
36 | return async (c: ComponentRef) => {
37 | const router = c.injector.get(Router);
38 | const { include = [], exclude = [] } = settings ?? {};
39 | const includeRules = normalizePathRules(include);
40 | const excludeRules = normalizePathRules(exclude);
41 | const subs = router
42 | .events
43 | .pipe(
44 | filter((event: any) => event instanceof NavigationEnd),
45 | skip(1), // Prevend double views on the first tigger (because GA Already send one ping on setup)
46 | filter(event => includeRules.length > 0
47 | ? includeRules.some(rule => rule.test(event.urlAfterRedirects))
48 | : true),
49 | filter(event => excludeRules.length > 0
50 | ? !excludeRules.some(rule => rule.test(event.urlAfterRedirects))
51 | : true)
52 | )
53 | .subscribe(event => gaService.pageView(event.urlAfterRedirects, undefined));
54 | // Cleanup
55 | c.onDestroy(() => subs.unsubscribe());
56 | };
57 | }
58 |
59 | /** Converts all path rules from string to Regex instances */
60 | function normalizePathRules(rules: Array): Array {
61 | return rules.map(rule => (rule instanceof RegExp)
62 | ? rule
63 | : new RegExp(`^${rule.replace('*', '.*')}$`, 'i'));
64 | }
65 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/initializers/google-analytics.initializer.ts:
--------------------------------------------------------------------------------
1 | import { Provider, APP_INITIALIZER, isDevMode } from '@angular/core';
2 | import { NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN } from '../tokens/ngx-google-analytics-settings-token';
3 | import { IGoogleAnalyticsSettings } from '../interfaces/i-google-analytics-settings';
4 | import { NGX_GTAG_FN } from '../tokens/ngx-gtag-token';
5 | import { GtagFn } from '../types/gtag.type';
6 | import { DOCUMENT } from '@angular/common';
7 |
8 | /**
9 | * Provide a DI Configuration to attach GA Initialization at Angular Startup Cycle.
10 | */
11 | export const NGX_GOOGLE_ANALYTICS_INITIALIZER_PROVIDER: Provider = {
12 | provide: APP_INITIALIZER,
13 | multi: true,
14 | useFactory: GoogleAnalyticsInitializer,
15 | deps: [
16 | NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN,
17 | NGX_GTAG_FN,
18 | DOCUMENT
19 | ]
20 | };
21 |
22 | /**
23 | * Create a script element on DOM and link it to Google Analytics tracking code URI.
24 | * After that, execute exactly same init process as tracking snippet code.
25 | */
26 | export function GoogleAnalyticsInitializer(
27 | settings: IGoogleAnalyticsSettings,
28 | gtag: GtagFn,
29 | document: Document
30 | ) {
31 | return async () => {
32 | if (!settings.trackingCode) {
33 | if (!isDevMode()) {
34 | console.error('Empty tracking code for Google Analytics. Make sure to provide one when initializing NgxGoogleAnalyticsModule.');
35 | }
36 |
37 | return;
38 | }
39 |
40 | if (!gtag) {
41 | if (!isDevMode()) {
42 | console.error('Was not possible create or read gtag() fn. Make sure this module is running on a Browser w/ access to Window interface.');
43 | }
44 |
45 | return;
46 | }
47 |
48 | if (!document) {
49 | if (!isDevMode()) {
50 | console.error('Was not possible to access Document interface. Make sure this module is running on a Browser w/ access do Document interface.');
51 | }
52 | }
53 |
54 | // Set default ga.js uri
55 | settings.uri = settings.uri || `https://www.googletagmanager.com/gtag/js?id=${settings.trackingCode}`;
56 |
57 | // these commands should run first!
58 | settings.initCommands = settings?.initCommands ?? [];
59 |
60 | // assert config command
61 | if (!settings.initCommands.find(x => x.command === 'config')) {
62 | settings.initCommands.unshift({ command: 'config', values: [ settings.trackingCode ] });
63 | }
64 |
65 | // assert js command
66 | if (!settings.initCommands.find(x => x.command === 'js')) {
67 | settings.initCommands.unshift({ command: 'js', values: [ new Date() ] });
68 | }
69 |
70 | for (const command of settings.initCommands) {
71 | gtag(command.command, ...command.values);
72 | }
73 |
74 | const s: HTMLScriptElement = document.createElement('script');
75 | s.async = true;
76 | s.src = settings.uri;
77 |
78 | if (settings.nonce) {
79 | s.setAttribute('nonce', settings.nonce);
80 | }
81 |
82 | const head: HTMLHeadElement = document.getElementsByTagName('head')[0];
83 | head.appendChild(s);
84 | };
85 | }
86 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/interfaces/i-google-analytics-command.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Standardizes a common command protocol :)
3 | */
4 | export interface IGoogleAnalyticsCommand {
5 | command: string;
6 | values: Array;
7 | }
8 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/interfaces/i-google-analytics-routing-settings.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Provide some custom settings for Automatics Router listener behaviour.
3 | */
4 | export interface IGoogleAnalyticsRoutingSettings {
5 | /**
6 | * Exclude the given path to the auto page-view trigger.
7 | *
8 | * ```ts
9 | * @NgModule({
10 | * imports: [
11 | * NgxGoogleAnalyticsModule.forRoot(...),
12 | * NgxGoogleAnalyticsRouterModule.forRoot({ exclude: ['/login', '/internal/*', /regExp/gi] })
13 | * ]
14 | * })
15 | * AppModule
16 | * ```
17 | */
18 | exclude?: Array;
19 |
20 | /**
21 | * Auto trigger page-view only for allowed uris.
22 | *
23 | * ```ts
24 | * @NgModule({
25 | * imports: [
26 | * NgxGoogleAnalyticsModule.forRoot(...),
27 | * NgxGoogleAnalyticsRouterModule.forRoot({ include: ['/login', '/internal/*', /regExp/gi] })
28 | * ]
29 | * })
30 | * AppModule
31 | * ```
32 | */
33 | include?: Array;
34 | }
35 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/interfaces/i-google-analytics-settings.ts:
--------------------------------------------------------------------------------
1 | import { IGoogleAnalyticsCommand } from './i-google-analytics-command';
2 |
3 | /**
4 | * Standardize an key-value objet to configure GA installation.
5 | */
6 | export interface IGoogleAnalyticsSettings {
7 | /** Is mandatory to provide a tracking code folks... */
8 | trackingCode: string;
9 | /** You can inject custom initialization commands like UserId or other e-commerce features. */
10 | initCommands?: Array;
11 | /** If Google changes the uri and I die, you can survive! */
12 | uri?: string;
13 | /** If true, trace GA tracking errors in production mode */
14 | enableTracing?: boolean;
15 | /** If has a value, nonce will be added to script tag **/
16 | nonce?: string;
17 | }
18 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/ngx-google-analytics-router/ngx-google-analytics-router.module.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders, NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER } from '../initializers/google-analytics-router.initializer';
4 | import { NgxGoogleAnalyticsModule } from '../ngx-google-analytics.module';
5 | import { IGoogleAnalyticsRoutingSettings } from '../interfaces/i-google-analytics-routing-settings';
6 | import { NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN } from '../tokens/ngx-google-analytics-router-settings-token';
7 |
8 | /**
9 | * Attach a listener to `NavigationEnd` Router event. So, every time Router finish the page resolution it should call `NavigationEnd` event.
10 | * We assume that NavigationEnd is the final page resolution and call GA `page_view` command.
11 | *
12 | * To avoid double binds, we also destroy the subscription when de Bootstrap Component is destroied. But, we don't know for sure
13 | * that this strategy does not cause double bind on multiple bootstrap components.
14 | *
15 | * We are using de component's injector reference to resolve Router, sou I hope there is no problem w/ double bing.
16 | *
17 | * If you have this problem, I encourage not Use NgxGoogleAnalyticsRouterModule and atach the listener on AppComponent initialization.
18 | *
19 | * This Module is just a sugar for:
20 | *
21 | ```typescript
22 | constructor(private router: Router) {}
23 | ...
24 | ngOnInit() {
25 | ...
26 | this.router
27 | .events
28 | .pipe(takeUntil(this.onDestroy$))
29 | .subscribe(event => {
30 | if (event instanceof NavigationEnd) {
31 | gaService.pageView(event.urlAfterRedirects, undefined);
32 | }
33 | });
34 | ```
35 | */
36 | @NgModule({
37 | imports: [
38 | CommonModule,
39 | NgxGoogleAnalyticsModule
40 | ],
41 | providers: [
42 | NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER
43 | ],
44 | declarations: []
45 | })
46 | export class NgxGoogleAnalyticsRouterModule {
47 | static forRoot(settings?: IGoogleAnalyticsRoutingSettings): ModuleWithProviders {
48 | return {
49 | ngModule: NgxGoogleAnalyticsRouterModule,
50 | providers: [
51 | {
52 | provide: NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN,
53 | useValue: settings ?? {}
54 | }
55 | ]
56 | };
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/ngx-google-analytics.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, ModuleWithProviders } from '@angular/core';
2 | import { IGoogleAnalyticsCommand } from './interfaces/i-google-analytics-command';
3 | import { NGX_GOOGLE_ANALYTICS_INITIALIZER_PROVIDER } from './initializers/google-analytics.initializer';
4 | import { NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN } from './tokens/ngx-google-analytics-settings-token';
5 | import { GaEventDirective } from './directives/ga-event.directive';
6 | import { GaEventCategoryDirective } from './directives/ga-event-category.directive';
7 | import { GaEventFormInputDirective } from './directives/ga-event-form-input.directive';
8 | import { IGoogleAnalyticsSettings } from './interfaces/i-google-analytics-settings';
9 |
10 | /**
11 | * Install Google Analytics Tracking code on your environment and configure tracking ID.
12 | *
13 | * This module should be a dependency on the highest level module of the application, i.e. AppModule in most use cases.
14 | */
15 | @NgModule({
16 | imports: [
17 | ],
18 | declarations: [
19 | GaEventDirective,
20 | GaEventCategoryDirective,
21 | GaEventFormInputDirective
22 | ],
23 | exports: [
24 | GaEventDirective,
25 | GaEventCategoryDirective,
26 | GaEventFormInputDirective
27 | ]
28 | })
29 | export class NgxGoogleAnalyticsModule {
30 | /**
31 | * You should provide a valid Google TrackingCode. This code will be provided to the entire application by
32 | * `NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN` token. You can inject this code in you components if you like by
33 | * use the following injection code `@Inject(NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN) gaConfig: IGoogleAnalyticsSettings`
34 | *
35 | * @param trackingCode The Google Tracking Code
36 | * @param initCommands When placed, it will run any GA Commands in sequence after setup GA environment.
37 | * @param uri When placed, it will change the default js URI to the provided one.
38 | * @param enableTracing When true, trace GA tracking errors on production mode.
39 | * @param nonce When placed, nonce will be added to script tag.
40 | */
41 | static forRoot(trackingCode: string, initCommands: IGoogleAnalyticsCommand[] = [], uri?: string, enableTracing?: boolean, nonce?: string): ModuleWithProviders {
42 | return {
43 | ngModule: NgxGoogleAnalyticsModule,
44 | providers: [
45 | {
46 | provide: NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN,
47 | useValue: {
48 | trackingCode,
49 | initCommands,
50 | uri,
51 | enableTracing,
52 | nonce
53 | } as IGoogleAnalyticsSettings
54 | },
55 | NGX_GOOGLE_ANALYTICS_INITIALIZER_PROVIDER,
56 | ]
57 | };
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/services/google-analytics.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { NgxGoogleAnalyticsModule } from '../ngx-google-analytics.module';
3 | import { GoogleAnalyticsService } from './google-analytics.service';
4 |
5 | describe('GoogleAnalyticsService', () => {
6 |
7 | window['dataLayer'] = [];
8 |
9 | window['gtag'] = function () {
10 | window['dataLayer'].push(arguments as any);
11 | };
12 |
13 | const tracking = 'GA-000000000';
14 | let spyOnConsole: jasmine.Spy,
15 | spyOnGtag: jasmine.Spy;
16 |
17 | beforeEach(() => {
18 | TestBed.configureTestingModule({
19 | imports: [
20 | NgxGoogleAnalyticsModule.forRoot(tracking)
21 | ]
22 | });
23 | });
24 |
25 | beforeEach(() => {
26 | spyOnConsole = spyOn(console, 'error');
27 | spyOnGtag = spyOn(window as any, 'gtag');
28 | });
29 |
30 | it('should call gtag fn w/ action/command pair', () => {
31 | const action = 'action',
32 | command = 'command',
33 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
34 | // act
35 | ga.gtag(action, command);
36 | // specs
37 | expect(spyOnGtag).toHaveBeenCalledWith(action, command);
38 | });
39 |
40 | describe('gtag(`event`)', () => {
41 |
42 | it('should call a `event` action on gtag command', () => {
43 | const action = 'video_auto_play_start',
44 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
45 |
46 | ga.event(action);
47 |
48 | expect(spyOnGtag).toHaveBeenCalledWith('event', action);
49 | });
50 |
51 | it('should find `event_category` property on gtag command', () => {
52 | const action = 'video_auto_play_start',
53 | event_category = 'video_auto_play',
54 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
55 |
56 | ga.event(action, event_category);
57 |
58 | expect(spyOnGtag).toHaveBeenCalledWith('event', action, { event_category });
59 | });
60 |
61 | it('should find `event_label` property on gtag command', () => {
62 | const action = 'video_auto_play_start',
63 | event_label = 'My promotional video',
64 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
65 |
66 | ga.event(action, undefined, event_label);
67 |
68 | expect(spyOnGtag).toHaveBeenCalledWith('event', action, { event_label });
69 | });
70 |
71 | it('should find `value` property on gtag command', () => {
72 | const action = 'video_auto_play_start',
73 | value = 40,
74 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
75 |
76 | ga.event(action, undefined, undefined, value);
77 |
78 | expect(spyOnGtag).toHaveBeenCalledWith('event', action, { value });
79 | });
80 |
81 | it('should find `interaction` property on gtag command', () => {
82 | const action = 'video_auto_play_start',
83 | interaction = true,
84 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
85 |
86 | ga.event(action, undefined, undefined, undefined, interaction);
87 |
88 | expect(spyOnGtag).toHaveBeenCalledWith('event', action, { interaction });
89 | });
90 |
91 | });
92 |
93 | describe('gtag(`config`) aka pageView', () => {
94 |
95 | it('should call a `config` action on gtag command', () => {
96 | const page_path = '/page.html',
97 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
98 |
99 | ga.pageView(page_path);
100 |
101 | expect(spyOnGtag).toHaveBeenCalledWith('config', tracking, { page_path, page_location: document.location.href });
102 | });
103 |
104 | it('should send `page_title` attribute on gtag command', () => {
105 | const page_path = '/page.html',
106 | page_title = 'My Page View',
107 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
108 |
109 | ga.pageView(page_path, page_title);
110 |
111 | expect(spyOnGtag).toHaveBeenCalledWith('config', tracking, { page_path, page_title, page_location: document.location.href });
112 | });
113 |
114 | it('should send `page_location` attribute on gtag command', () => {
115 | const page_path = '/page.html',
116 | page_location = 'my location',
117 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
118 |
119 | ga.pageView(page_path, undefined, page_location);
120 |
121 | expect(spyOnGtag).toHaveBeenCalledWith('config', tracking, { page_path, page_location });
122 | });
123 |
124 | it('should use `document.location.href` as a default `page_location`', () => {
125 | const page_path = '/page.html',
126 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
127 |
128 | ga.pageView(page_path, undefined);
129 |
130 | expect(spyOnGtag).toHaveBeenCalledWith('config', tracking, { page_path, page_location: document.location.href });
131 | });
132 |
133 | });
134 |
135 | describe('gtag(`event`)', () => {
136 |
137 | it('should call a `event` action on gtag command', () => {
138 | const screen_name = 'Home Screen',
139 | app_name = 'My App',
140 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
141 |
142 | ga.appView(screen_name, app_name);
143 |
144 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'screen_view', { screen_name, app_name });
145 | });
146 |
147 | it('should send `app_id` property on gtag command', () => {
148 | const screen_name = 'Home Screen',
149 | app_name = 'My App',
150 | app_id = '2333',
151 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
152 |
153 | ga.appView(screen_name, app_name, app_id);
154 |
155 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'screen_view', { screen_name, app_name, app_id });
156 | });
157 |
158 | it('should send `app_version` property on gtag command', () => {
159 | const screen_name = 'Home Screen',
160 | app_name = 'My App',
161 | app_version = 'v1.0',
162 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
163 |
164 | ga.appView(screen_name, app_name, undefined, app_version);
165 |
166 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'screen_view', { screen_name, app_name, app_version });
167 | });
168 |
169 | it('should send `app_installer_id` property on gtag command', () => {
170 | const screen_name = 'Home Screen',
171 | app_name = 'My App',
172 | app_installer_id = '30000',
173 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
174 |
175 | ga.appView(screen_name, app_name, undefined, undefined, app_installer_id);
176 |
177 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'screen_view', { screen_name, app_name, app_installer_id });
178 | });
179 |
180 | });
181 |
182 |
183 | describe('gtag(`event`, `exception`)', () => {
184 |
185 | it('should call `event` action w/ `exception type on gtag command`', () => {
186 | const ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
187 |
188 | ga.exception();
189 |
190 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'exception');
191 | });
192 |
193 | it('should send `description` attribute on gtag command', () => {
194 | const description = 'Deu muito ruim',
195 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
196 |
197 | ga.exception(description);
198 |
199 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'exception', { description });
200 | });
201 |
202 | it('should send `fatal` attribute on gtag command', () => {
203 | const fatal = true,
204 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
205 |
206 | ga.exception(undefined, fatal);
207 |
208 | expect(spyOnGtag).toHaveBeenCalledWith('event', 'exception', { fatal });
209 | });
210 |
211 | });
212 |
213 | describe('gtag(`set`, ...)', () => {
214 |
215 | it('should send `set` command on gtag() call', () => {
216 | const setData = { currency: 'USD', country: 'US' },
217 | ga: GoogleAnalyticsService = TestBed.inject(GoogleAnalyticsService);
218 |
219 | ga.set(setData);
220 |
221 | expect(spyOnGtag).toHaveBeenCalledWith('set', setData);
222 | });
223 | });
224 |
225 | });
226 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/services/google-analytics.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, Inject, isDevMode } from '@angular/core';
2 | import { NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN } from '../tokens/ngx-google-analytics-settings-token';
3 | import { IGoogleAnalyticsSettings } from '../interfaces/i-google-analytics-settings';
4 | import { GaActionEnum } from '../enums/ga-action.enum';
5 | import { DOCUMENT } from '@angular/common';
6 | import { NGX_GTAG_FN } from '../tokens/ngx-gtag-token';
7 | import { GtagFn } from '../types/gtag.type';
8 |
9 | @Injectable({
10 | providedIn: 'root'
11 | })
12 | export class GoogleAnalyticsService {
13 |
14 | private get document(): Document {
15 | return this._document;
16 | }
17 |
18 | constructor(
19 | @Inject(NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN) private readonly settings: IGoogleAnalyticsSettings,
20 | @Inject(DOCUMENT) private readonly _document: any,
21 | @Inject(NGX_GTAG_FN) private readonly _gtag: GtagFn
22 | ) { }
23 |
24 | private throw(err: Error) {
25 | if ((this.settings.enableTracing || isDevMode()) && console && console.error) {
26 | console.error(err);
27 | }
28 | }
29 |
30 | /** @todo Change this to `Object.fromEntity()` in the future... */
31 | private toKeyValue(map: Map): { [param: string]: any } | void {
32 | return (map.size > 0)
33 | ? Array.from(map).reduce(
34 | (obj, [key, value]) => Object.defineProperty(obj, key, { value, enumerable: true }),
35 | {}
36 | )
37 | : undefined;
38 | }
39 |
40 | /**
41 | * Call native GA Tag
42 | */
43 | gtag(...args: any[]) {
44 | try {
45 | this._gtag(...args.filter(x => x !== undefined));
46 | } catch (err: any) {
47 | this.throw(err);
48 | }
49 | }
50 |
51 | /**
52 | * Send an event trigger to GA. It is the same as call:
53 | * ```js
54 | * gtag('event', 'video_auto_play_start', {
55 | * 'event_label': 'My promotional video',
56 | * 'event_category': 'video_auto_play'
57 | * });
58 | * ```
59 | *
60 | * @param action 'video_auto_play_start'
61 | * @param category 'video_auto_play'
62 | * @param label 'My promotional video'
63 | * @param value An value to measure something
64 | * @param interaction If user interaction is performed
65 | */
66 | event(action: GaActionEnum | string, category?: string, label?: string, value?: number, interaction?: boolean, options?: Object) {
67 | try {
68 | const opt = new Map();
69 | if (category) {
70 | opt.set('event_category', category);
71 | }
72 | if (label) {
73 | opt.set('event_label', label);
74 | }
75 | if (value) {
76 | opt.set('value', value);
77 | }
78 | if (interaction !== undefined) {
79 | opt.set('interaction', interaction);
80 | }
81 | if (options) {
82 | Object
83 | .entries(options)
84 | .map(([key, value]) => opt.set(key, value));
85 | }
86 | const params = this.toKeyValue(opt);
87 | if (params) {
88 | this.gtag('event', action as string, params);
89 | } else {
90 | this.gtag('event', action as string);
91 | }
92 | } catch (error: any) {
93 | this.throw(error);
94 | }
95 | }
96 |
97 | /**
98 | * Send an page view event. This is the same as
99 | *
100 | * ```js
101 | * gtag('config', 'GA_TRACKING_ID', {
102 | * 'page_title' : 'Homepage',
103 | * 'page_path': '/home'
104 | * });
105 | * ```
106 | *
107 | * The tracking ID is injected automatically by Inject Token NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN
108 | *
109 | * @param path /home
110 | * @param title Homepage
111 | * @param location '{ page_location }'
112 | * @param options '{ ... custom dimentions }'
113 | */
114 | pageView( path: string, title?: string, location?: string, options?: Object) {
115 | try {
116 | const opt = new Map([['page_path', path]]);
117 | if (title) {
118 | opt.set('page_title', title);
119 | }
120 | if (location || this.document) {
121 | opt.set('page_location', (location || this.document.location.href));
122 | }
123 | if (options) {
124 | Object
125 | .entries(options)
126 | .map(([key, value]) => opt.set(key, value));
127 | }
128 | this.gtag('config', this.settings.trackingCode, this.toKeyValue(opt));
129 | } catch (error: any) {
130 | this.throw(error);
131 | }
132 | }
133 |
134 | /**
135 | * Send an event to report a App Page View. It is the same as
136 | *
137 | * ```js
138 | * gtag('event', 'screen_view', {
139 | * 'app_name': 'myAppName',
140 | * 'screen_name' : 'Home'
141 | * });
142 | *
143 | * ```
144 | *
145 | * @param screen 'screen_name'
146 | * @param appName 'app_name'
147 | * @param appId 'app_id'
148 | * @param appVersion 'app_version'
149 | * @param installerId 'app_installer_id'
150 | */
151 | appView(screen: string, appName: string, appId?: string, appVersion?: string, installerId?: string) {
152 | try {
153 | const opt = new Map([['screen_name', screen], ['app_name', appName]]);
154 | if (appId) {
155 | opt.set('app_id', appId);
156 | }
157 | if (appVersion) {
158 | opt.set('app_version', appVersion);
159 | }
160 | if (installerId) {
161 | opt.set('app_installer_id', installerId);
162 | }
163 | this.gtag('event', 'screen_view', this.toKeyValue(opt));
164 | } catch (error: any) {
165 | this.throw(error);
166 | }
167 | }
168 |
169 | /**
170 | * Defines persistent values on GoogleAnalytics
171 | *
172 | * @see https://developers.google.com/analytics/devguides/collection/gtagjs/setting-values
173 | *
174 | * ```js
175 | * gtag('set', {
176 | * 'currency': 'USD',
177 | * 'country': 'US'
178 | * });
179 | * ```
180 | */
181 | set(...options: Array) {
182 | try {
183 | this._gtag('set', ...options);
184 | } catch (err: any) {
185 | this.throw(err);
186 | }
187 | }
188 |
189 | /**
190 | * Send an event to GA to report an application error. It is the same as
191 | *
192 | * ```js
193 | * gtag('event', 'exception', {
194 | * 'description': 'error_description',
195 | * 'fatal': false // set to true if the error is fatal
196 | * });
197 | * ```
198 | *
199 | * @param description 'error_description'
200 | * @param fatal set to true if the error is fatal
201 | */
202 | exception(description?: string, fatal?: boolean) {
203 | try {
204 | const opt = new Map();
205 | if (description) {
206 | opt.set('description', description);
207 | }
208 | if (fatal) {
209 | opt.set('fatal', fatal);
210 | }
211 | const params = this.toKeyValue(opt);
212 | if (params) {
213 | this.gtag('event', 'exception', this.toKeyValue(opt));
214 | } else {
215 | this.gtag('event', 'exception');
216 | }
217 | } catch (error: any) {
218 | this.throw(error);
219 | }
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/tokens/ngx-data-layer-token.ts:
--------------------------------------------------------------------------------
1 | import { DataLayer } from '../types/data-layer.type';
2 | import { InjectionToken, inject } from '@angular/core';
3 | import { NGX_WINDOW } from './ngx-window-token';
4 | import { GaWindow } from './ngx-google-analytics-window';
5 |
6 | /**
7 | * Check if there is some global function called gtag on Window object, or create an empty function to doesn't brake codes...
8 | */
9 | export function getDataLayerFn(window: GaWindow): DataLayer {
10 | return (window)
11 | ? window['dataLayer'] = window['dataLayer'] || []
12 | : null;
13 | }
14 |
15 | /**
16 | * Provides an injection token to access Google Analytics DataLayer Collection
17 | */
18 | export const NGX_DATA_LAYER = new InjectionToken('ngx-data-layer', {
19 | providedIn: 'root',
20 | factory: () => getDataLayerFn(inject(NGX_WINDOW))
21 | });
22 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/tokens/ngx-google-analytics-router-settings-token.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 | import { IGoogleAnalyticsRoutingSettings } from '../interfaces/i-google-analytics-routing-settings';
3 |
4 | /**
5 | * Provide a Injection Token to global settings.
6 | */
7 | export const NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN = new InjectionToken('ngx-google-analytics-routing-settings', {
8 | factory: () => ({})
9 | });
10 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/tokens/ngx-google-analytics-settings-token.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 | import { IGoogleAnalyticsSettings } from '../interfaces/i-google-analytics-settings';
3 |
4 | /**
5 | * Provide a Injection Token to global settings.
6 | */
7 | export const NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN = new InjectionToken('ngx-google-analytics-settings', {
8 | factory: () => ({ trackingCode: '', enableTracing: false })
9 | });
10 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/tokens/ngx-google-analytics-window.ts:
--------------------------------------------------------------------------------
1 | export type GaWindow = Window & {
2 | gtag?: any;
3 | dataLayer?: any;
4 | }
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/tokens/ngx-gtag-token.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken, inject } from '@angular/core';
2 | import { NGX_WINDOW } from './ngx-window-token';
3 | import { GtagFn } from '../types/gtag.type';
4 | import { DataLayer } from '../types/data-layer.type';
5 | import { NGX_DATA_LAYER } from './ngx-data-layer-token';
6 | import { GaWindow } from './ngx-google-analytics-window';
7 |
8 | /**
9 | * Check if there is some global function called gtag on Window object, or create an empty function to doesn't brake codes...
10 | */
11 | export function getGtagFn(window: GaWindow, dataLayer: DataLayer): GtagFn {
12 | return (window)
13 | ? window['gtag'] = window['gtag'] || function () {
14 | dataLayer.push(arguments as any);
15 | }
16 | : null;
17 | }
18 |
19 | /**
20 | * Provides an injection token to access Google Analytics Gtag Function
21 | */
22 | export const NGX_GTAG_FN = new InjectionToken('ngx-gtag-fn', {
23 | providedIn: 'root',
24 | factory: () => getGtagFn(inject(NGX_WINDOW), inject(NGX_DATA_LAYER))
25 | });
26 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/tokens/ngx-window-token.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken, inject } from '@angular/core';
2 | import { DOCUMENT } from '@angular/common';
3 | import { GaWindow } from './ngx-google-analytics-window';
4 |
5 | /**
6 | * Provide DOM Window reference.
7 | */
8 | export const NGX_WINDOW = new InjectionToken('ngx-window', {
9 | providedIn: 'root',
10 | factory: () => {
11 | const { defaultView } = inject(DOCUMENT);
12 |
13 | if (!defaultView) {
14 | throw new Error('Window is not available');
15 | }
16 |
17 | return defaultView;
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/types/data-layer.type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Provides an interface os a GA command list.
3 | */
4 | export type DataLayer = Array<(string | { [param: string]: string })>;
5 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/types/ga-action.type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A string that represents a default GA action used by Google to generate e-commerce inteligence.
3 | *
4 | * You can provide a custom string as well.
5 | * @deprecated use lib/enums/ga-action.enum.ts instead
6 | */
7 | export type GaAction = 'view_search_results'
8 | | 'add_payment_info'
9 | | 'add_to_cart'
10 | | 'add_to_wishlist'
11 | | 'begin_checkout'
12 | | 'checkout_progress'
13 | | 'generate_lead'
14 | | 'login'
15 | | 'purchase'
16 | | 'refund'
17 | | 'remove_from_cart'
18 | | 'search'
19 | | 'select_content'
20 | | 'set_checkout_option'
21 | | 'share'
22 | | 'sign_up'
23 | | 'view_item'
24 | | 'view_item_list'
25 | | 'view_promotion';
26 |
27 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/lib/types/gtag.type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Google Analytics GTagFn call signature
3 | */
4 | export type GtagFn = (...args: (string | { [param: string]: string })[]) => {};
5 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/public_api.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Public API Surface of ngx-google-analytics
3 | */
4 |
5 | export * from './lib/directives/ga-event-category.directive';
6 | export * from './lib/directives/ga-event.directive';
7 | export * from './lib/directives/ga-event-form-input.directive';
8 |
9 | export * from './lib/enums/ga-action.enum';
10 |
11 | export * from './lib/initializers/google-analytics.initializer';
12 | export * from './lib/initializers/google-analytics-router.initializer';
13 |
14 | export * from './lib/interfaces/i-google-analytics-command';
15 | export * from './lib/interfaces/i-google-analytics-routing-settings';
16 | export * from './lib/interfaces/i-google-analytics-settings';
17 |
18 | export * from './lib/services/google-analytics.service';
19 |
20 | export * from './lib/tokens/ngx-data-layer-token';
21 | export * from './lib/tokens/ngx-google-analytics-router-settings-token';
22 | export * from './lib/tokens/ngx-google-analytics-settings-token';
23 | export * from './lib/tokens/ngx-gtag-token';
24 | export * from './lib/tokens/ngx-window-token';
25 |
26 | export * from './lib/types/data-layer.type';
27 | export * from './lib/types/ga-action.type';
28 | export * from './lib/types/gtag.type';
29 |
30 | export * from './lib/ngx-google-analytics.module';
31 | export * from './lib/ngx-google-analytics-router/ngx-google-analytics-router.module';
32 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import {
7 | BrowserDynamicTestingModule,
8 | platformBrowserDynamicTesting
9 | } from '@angular/platform-browser-dynamic/testing';
10 |
11 | declare const require: any;
12 |
13 | // First, initialize the Angular testing environment.
14 | getTestBed().initTestEnvironment(
15 | BrowserDynamicTestingModule,
16 | platformBrowserDynamicTesting(), {
17 | teardown: { destroyAfterEach: false }
18 | }
19 | );
20 | // Then we find all the tests.
21 | const context = require.context('./', true, /\.spec\.ts$/);
22 | // And load the modules.
23 | context.keys().map(context);
24 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/tsconfig.lib.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/lib",
5 | "declarationMap": true,
6 | "target": "es2015",
7 | "module": "es2015",
8 | "moduleResolution": "node",
9 | "declaration": true,
10 | "sourceMap": true,
11 | "inlineSources": true,
12 | "experimentalDecorators": true,
13 | "importHelpers": true,
14 | "types": [],
15 | "lib": [
16 | "dom",
17 | "es2017"
18 | ]
19 | },
20 | "angularCompilerOptions": {
21 | "skipTemplateCodegen": true,
22 | "strictMetadataEmit": true,
23 | "fullTemplateTypeCheck": true,
24 | "strictInjectionParameters": true,
25 | "enableResourceInlining": true
26 | },
27 | "exclude": [
28 | "src/test.ts",
29 | "**/*.spec.ts"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/tsconfig.lib.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.lib.json",
3 | "compilerOptions": {
4 | "declarationMap": false
5 | },
6 | "angularCompilerOptions": {
7 | "compilationMode": "partial"
8 | }
9 | }
--------------------------------------------------------------------------------
/projects/ngx-google-analytics/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "src/test.ts"
12 | ],
13 | "include": [
14 | "**/*.spec.ts",
15 | "**/*.d.ts"
16 | ]
17 | }
18 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { RouterModule, Routes } from '@angular/router';
2 | import { NgModule } from '@angular/core';
3 | import { TestPageAComponent } from './test-page-a/test-page-a.component';
4 | import { TestPageBComponent } from './test-page-b/test-page-b.component';
5 |
6 | const routes: Routes = [
7 | {
8 | path: 'page-1',
9 | component: TestPageAComponent
10 | },
11 | {
12 | path: 'page-2',
13 | component: TestPageBComponent
14 | }
15 | ];
16 |
17 | @NgModule({
18 | imports: [
19 | RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' })
20 | ],
21 | exports: [
22 | RouterModule
23 | ]
24 | })
25 | export class AppRoutingModule { }
26 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxandriani/ngx-google-analytics/2dc6bab54ef0a2104e39977e9385291427edf60f/src/app/app.component.css
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Welcome to {{ title }}!
5 |
6 |
7 |
8 |
9 | Page 1
10 | Page 2
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Directive tests
18 |
19 |
20 | Click Test
21 | Focus Test
22 | Blur Test
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Group Directive Test
30 |
31 |
32 | Click Test
33 | Focus Test
34 | Blur Test
35 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, waitForAsync } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 |
4 | describe('AppComponent', () => {
5 | beforeEach(waitForAsync(() => {
6 | TestBed.configureTestingModule({
7 | declarations: [
8 | AppComponent
9 | ],
10 | }).compileComponents();
11 | }));
12 |
13 | it('should create the app', () => {
14 | const fixture = TestBed.createComponent(AppComponent);
15 | const app = fixture.debugElement.componentInstance;
16 | expect(app).toBeTruthy();
17 | });
18 |
19 | it(`should have as title 'ngx-google-analytics-sdk'`, () => {
20 | const fixture = TestBed.createComponent(AppComponent);
21 | const app = fixture.debugElement.componentInstance;
22 | expect(app.title).toEqual('ngx-google-analytics-sdk');
23 | });
24 |
25 | it('should render title in a h1 tag', () => {
26 | const fixture = TestBed.createComponent(AppComponent);
27 | fixture.detectChanges();
28 | const compiled = fixture.debugElement.nativeElement;
29 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to ngx-google-analytics-sdk!');
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'ga-app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.css']
7 | })
8 | export class AppComponent {
9 | title = 'ngx-google-analytics-sdk';
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 |
4 | import { AppComponent } from './app.component';
5 | import { NgxGoogleAnalyticsModule, NgxGoogleAnalyticsRouterModule } from 'ngx-google-analytics';
6 | import { environment } from 'src/environments/environment';
7 | import { TestPageAComponent } from './test-page-a/test-page-a.component';
8 | import { TestPageBComponent } from './test-page-b/test-page-b.component';
9 | import { RouterModule } from '@angular/router';
10 | import { AppRoutingModule } from './app-routing.module';
11 |
12 | @NgModule({
13 | declarations: [
14 | AppComponent,
15 | TestPageAComponent,
16 | TestPageBComponent
17 | ],
18 | imports: [
19 | BrowserModule,
20 | AppRoutingModule,
21 | RouterModule,
22 | NgxGoogleAnalyticsModule.forRoot(environment.ga),
23 | NgxGoogleAnalyticsRouterModule.forRoot({ include: [ '/page-*' ] }),
24 | ],
25 | bootstrap: [AppComponent]
26 | })
27 | export class AppModule { }
28 |
--------------------------------------------------------------------------------
/src/app/test-page-a/test-page-a.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxandriani/ngx-google-analytics/2dc6bab54ef0a2104e39977e9385291427edf60f/src/app/test-page-a/test-page-a.component.css
--------------------------------------------------------------------------------
/src/app/test-page-a/test-page-a.component.html:
--------------------------------------------------------------------------------
1 |
2 | test-page-a works!
3 |
4 |
5 |
Back
--------------------------------------------------------------------------------
/src/app/test-page-a/test-page-a.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { TestPageAComponent } from './test-page-a.component';
4 |
5 | describe('TestPageAComponent', () => {
6 | let component: TestPageAComponent;
7 | let fixture: ComponentFixture
;
8 |
9 | beforeEach(waitForAsync(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ TestPageAComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(TestPageAComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/test-page-a/test-page-a.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'ga-app-test-page-a',
5 | templateUrl: './test-page-a.component.html',
6 | styleUrls: ['./test-page-a.component.css']
7 | })
8 | export class TestPageAComponent {
9 | }
10 |
--------------------------------------------------------------------------------
/src/app/test-page-b/test-page-b.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxandriani/ngx-google-analytics/2dc6bab54ef0a2104e39977e9385291427edf60f/src/app/test-page-b/test-page-b.component.css
--------------------------------------------------------------------------------
/src/app/test-page-b/test-page-b.component.html:
--------------------------------------------------------------------------------
1 |
2 | test-page-b works!
3 |
4 |
5 | Back
--------------------------------------------------------------------------------
/src/app/test-page-b/test-page-b.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { TestPageBComponent } from './test-page-b.component';
4 |
5 | describe('TestPageBComponent', () => {
6 | let component: TestPageBComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(waitForAsync(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ TestPageBComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(TestPageBComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/test-page-b/test-page-b.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'ga-app-test-page-b',
5 | templateUrl: './test-page-b.component.html',
6 | styleUrls: ['./test-page-b.component.css']
7 | })
8 | export class TestPageBComponent {
9 | }
10 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxandriani/ngx-google-analytics/2dc6bab54ef0a2104e39977e9385291427edf60f/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | ga: 'tracking-code'
4 | };
5 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | ga: 'UA-17886362-3'
8 | };
9 |
10 | /*
11 | * For easier debugging in development mode, you can import the following file
12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
13 | *
14 | * This import should be commented out in production mode because it will have a negative impact
15 | * on performance if an error is thrown.
16 | */
17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
18 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/maxandriani/ngx-google-analytics/2dc6bab54ef0a2104e39977e9385291427edf60f/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NgxGoogleAnalyticsSdk
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client: {
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, '../coverage'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
22 | },
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
14 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | /**
7 |
8 | * By default, zone.js will patch all possible macroTask and DomEvents
9 |
10 | * user can disable parts of macroTask/DomEvents patch by setting following flags
11 |
12 | */
13 |
14 |
15 |
16 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
17 |
18 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
19 |
20 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
21 |
22 |
23 |
24 | /*
25 |
26 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
27 |
28 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
29 |
30 | */
31 |
32 | // (window as any).__Zone_enable_cross_context_check = true;
33 |
34 |
35 |
36 | /***************************************************************************************************
37 |
38 | * Zone JS is required by default for Angular itself.
39 |
40 | */
41 |
42 | import 'zone.js'; // Included with Angular CLI.
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | /***************************************************************************************************
51 |
52 | * APPLICATION IMPORTS
53 |
54 | */
55 |
56 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting(), {
16 | teardown: { destroyAfterEach: false }
17 | }
18 | );
19 | // Then we find all the tests.
20 | const context = require.context('./', true, /\.spec\.ts$/);
21 | // And load the modules.
22 | context.keys().map(context);
23 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "types": []
6 | },
7 | "files": [
8 | "main.ts",
9 | "polyfills.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": [
11 | "test.ts",
12 | "polyfills.ts"
13 | ],
14 | "include": [
15 | "**/*.spec.ts",
16 | "**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "baseUrl": "./",
5 | "downlevelIteration": true,
6 | "importHelpers": true,
7 | "outDir": "./dist/out-tsc",
8 | "sourceMap": true,
9 | "declaration": false,
10 | "module": "es2020",
11 | "moduleResolution": "node",
12 | "experimentalDecorators": true,
13 | "target": "es2015",
14 | "typeRoots": [
15 | "node_modules/@types"
16 | ],
17 | "lib": [
18 | "es2017",
19 | "dom"
20 | ],
21 | "paths": {
22 | "ngx-google-analytics": [
23 | "dist/ngx-google-analytics"
24 | ],
25 | "ngx-google-analytics/*": [
26 | "dist/ngx-google-analytics/*"
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------