UNPKG

ngx-google-analytics

Version:

A simple ng-9 wrapper to load Google Analytics dependency by angular way

686 lines (668 loc) 27.6 kB
import * as i0 from '@angular/core'; import { Directive, Input, InjectionToken, inject, isDevMode, Injectable, Inject, Optional, Host, APP_INITIALIZER, APP_BOOTSTRAP_LISTENER, NgModule } from '@angular/core'; import { fromEvent } from 'rxjs'; import { DOCUMENT, CommonModule } from '@angular/common'; import { __awaiter } from 'tslib'; import { Router, NavigationEnd } from '@angular/router'; import { filter, skip } from 'rxjs/operators'; class GaEventCategoryDirective { constructor() { } } GaEventCategoryDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GaEventCategoryDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); GaEventCategoryDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.2", type: GaEventCategoryDirective, selector: "[gaEvent][gaCategory],\n [gaCategory]", inputs: { gaCategory: "gaCategory" }, exportAs: ["gaCategory"], ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GaEventCategoryDirective, decorators: [{ type: Directive, args: [{ selector: `[gaEvent][gaCategory], [gaCategory]`, exportAs: 'gaCategory' }] }], ctorParameters: function () { return []; }, propDecorators: { gaCategory: [{ type: Input }] } }); /** * Provide a Injection Token to global settings. */ const NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN = new InjectionToken('ngx-google-analytics-settings', { factory: () => ({ trackingCode: '', enableTracing: false }) }); /** * Provide DOM Window reference. */ const NGX_WINDOW = new InjectionToken('ngx-window', { providedIn: 'root', factory: () => { const { defaultView } = inject(DOCUMENT); if (!defaultView) { throw new Error('Window is not available'); } return defaultView; }, }); /** * Check if there is some global function called gtag on Window object, or create an empty function to doesn't brake codes... */ function getDataLayerFn(window) { return (window) ? window['dataLayer'] = window['dataLayer'] || [] : null; } /** * Provides an injection token to access Google Analytics DataLayer Collection */ const NGX_DATA_LAYER = new InjectionToken('ngx-data-layer', { providedIn: 'root', factory: () => getDataLayerFn(inject(NGX_WINDOW)) }); /** * Check if there is some global function called gtag on Window object, or create an empty function to doesn't brake codes... */ function getGtagFn(window, dataLayer) { return (window) ? window['gtag'] = window['gtag'] || function () { dataLayer.push(arguments); } : null; } /** * Provides an injection token to access Google Analytics Gtag Function */ const NGX_GTAG_FN = new InjectionToken('ngx-gtag-fn', { providedIn: 'root', factory: () => getGtagFn(inject(NGX_WINDOW), inject(NGX_DATA_LAYER)) }); class GoogleAnalyticsService { constructor(settings, _document, _gtag) { this.settings = settings; this._document = _document; this._gtag = _gtag; } get document() { return this._document; } throw(err) { if ((this.settings.enableTracing || isDevMode()) && console && console.error) { console.error(err); } } /** @todo Change this to `Object.fromEntity()` in the future... */ toKeyValue(map) { return (map.size > 0) ? Array.from(map).reduce((obj, [key, value]) => Object.defineProperty(obj, key, { value, enumerable: true }), {}) : undefined; } /** * Call native GA Tag */ gtag(...args) { try { this._gtag(...args.filter(x => x !== undefined)); } catch (err) { this.throw(err); } } /** * Send an event trigger to GA. It is the same as call: * ```js * gtag('event', 'video_auto_play_start', { * 'event_label': 'My promotional video', * 'event_category': 'video_auto_play' * }); * ``` * * @param action 'video_auto_play_start' * @param category 'video_auto_play' * @param label 'My promotional video' * @param value An value to measure something * @param interaction If user interaction is performed */ event(action, category, label, value, interaction, options) { try { const opt = new Map(); if (category) { opt.set('event_category', category); } if (label) { opt.set('event_label', label); } if (value) { opt.set('value', value); } if (interaction !== undefined) { opt.set('interaction', interaction); } if (options) { Object .entries(options) .map(([key, value]) => opt.set(key, value)); } const params = this.toKeyValue(opt); if (params) { this.gtag('event', action, params); } else { this.gtag('event', action); } } catch (error) { this.throw(error); } } /** * Send an page view event. This is the same as * * ```js * gtag('config', 'GA_TRACKING_ID', { * 'page_title' : 'Homepage', * 'page_path': '/home' * }); * ``` * * The tracking ID is injected automatically by Inject Token NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN * * @param path /home * @param title Homepage * @param location '{ page_location }' * @param options '{ ... custom dimentions }' */ pageView(path, title, location, options) { try { const opt = new Map([['page_path', path]]); if (title) { opt.set('page_title', title); } if (location || this.document) { opt.set('page_location', (location || this.document.location.href)); } if (options) { Object .entries(options) .map(([key, value]) => opt.set(key, value)); } this.gtag('config', this.settings.trackingCode, this.toKeyValue(opt)); } catch (error) { this.throw(error); } } /** * Send an event to report a App Page View. It is the same as * * ```js * gtag('event', 'screen_view', { * 'app_name': 'myAppName', * 'screen_name' : 'Home' * }); * * ``` * * @param screen 'screen_name' * @param appName 'app_name' * @param appId 'app_id' * @param appVersion 'app_version' * @param installerId 'app_installer_id' */ appView(screen, appName, appId, appVersion, installerId) { try { const opt = new Map([['screen_name', screen], ['app_name', appName]]); if (appId) { opt.set('app_id', appId); } if (appVersion) { opt.set('app_version', appVersion); } if (installerId) { opt.set('app_installer_id', installerId); } this.gtag('event', 'screen_view', this.toKeyValue(opt)); } catch (error) { this.throw(error); } } /** * Defines persistent values on GoogleAnalytics * * @see https://developers.google.com/analytics/devguides/collection/gtagjs/setting-values * * ```js * gtag('set', { * 'currency': 'USD', * 'country': 'US' * }); * ``` */ set(...options) { try { this._gtag('set', ...options); } catch (err) { this.throw(err); } } /** * Send an event to GA to report an application error. It is the same as * * ```js * gtag('event', 'exception', { * 'description': 'error_description', * 'fatal': false // set to true if the error is fatal * }); * ``` * * @param description 'error_description' * @param fatal set to true if the error is fatal */ exception(description, fatal) { try { const opt = new Map(); if (description) { opt.set('description', description); } if (fatal) { opt.set('fatal', fatal); } const params = this.toKeyValue(opt); if (params) { this.gtag('event', 'exception', this.toKeyValue(opt)); } else { this.gtag('event', 'exception'); } } catch (error) { this.throw(error); } } } GoogleAnalyticsService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GoogleAnalyticsService, deps: [{ token: NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN }, { token: DOCUMENT }, { token: NGX_GTAG_FN }], target: i0.ɵɵFactoryTarget.Injectable }); GoogleAnalyticsService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GoogleAnalyticsService, providedIn: 'root' }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GoogleAnalyticsService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN] }] }, { type: undefined, decorators: [{ type: Inject, args: [DOCUMENT] }] }, { type: undefined, decorators: [{ type: Inject, args: [NGX_GTAG_FN] }] }]; } }); class GaEventDirective { constructor(gaCategoryDirective, gaService, settings, el) { this.gaCategoryDirective = gaCategoryDirective; this.gaService = gaService; this.settings = settings; this.el = el; this.gaBind = 'click'; } set gaBind(gaBind) { if (this.bindSubscription) { this.bindSubscription.unsubscribe(); } this._gaBind = gaBind; this.bindSubscription = fromEvent(this.el.nativeElement, gaBind).subscribe(() => this.trigger()); } get gaBind() { return this._gaBind; } ngOnDestroy() { if (this.bindSubscription) { this.bindSubscription.unsubscribe(); } } trigger() { try { // Observação: não é obrigatório especificar uma categoria, uma etiqueta ou um valor. Consulte Eventos padrão do Google Analytics abaixo. // if (!this.$gaCategoryDirective) { // throw new Error('You must provide a gaCategory attribute w/ gaEvent Directive.'); // } if (!this.gaAction && !this.gaEvent) { throw new Error('You must provide a gaAction attribute to identify this event.'); } this.gaService .event(this.gaAction || this.gaEvent, (this.gaCategoryDirective) ? this.gaCategoryDirective.gaCategory : undefined, this.gaLabel || this.label, this.gaValue, this.gaInteraction); } catch (err) { this.throw(err); } } throw(err) { if ((isDevMode() || this.settings.enableTracing) && console && console.warn) { console.warn(err); } } } GaEventDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GaEventDirective, deps: [{ token: GaEventCategoryDirective, optional: true }, { token: GoogleAnalyticsService }, { token: NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive }); GaEventDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.2", type: GaEventDirective, selector: "[gaEvent]", inputs: { gaAction: "gaAction", gaLabel: "gaLabel", label: "label", gaValue: "gaValue", gaInteraction: "gaInteraction", gaEvent: "gaEvent", gaBind: "gaBind" }, exportAs: ["gaEvent"], ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GaEventDirective, decorators: [{ type: Directive, args: [{ selector: `[gaEvent]`, exportAs: 'gaEvent' }] }], ctorParameters: function () { return [{ type: GaEventCategoryDirective, decorators: [{ type: Optional }] }, { type: GoogleAnalyticsService }, { type: undefined, decorators: [{ type: Inject, args: [NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN] }] }, { type: i0.ElementRef }]; }, propDecorators: { gaAction: [{ type: Input }], gaLabel: [{ type: Input }], label: [{ type: Input }], gaValue: [{ type: Input }], gaInteraction: [{ type: Input }], gaEvent: [{ type: Input }], gaBind: [{ type: Input }] } }); class GaEventFormInputDirective { constructor(gaEvent) { this.gaEvent = gaEvent; this.gaBind = 'focus'; } set gaBind(bind) { if (this.gaEvent) { this.gaEvent.gaBind = bind; } } } GaEventFormInputDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GaEventFormInputDirective, deps: [{ token: GaEventDirective, host: true, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); GaEventFormInputDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.0.2", type: GaEventFormInputDirective, selector: "input[gaEvent],\n select[gaEvent],\n textarea[gaEvent]", inputs: { gaBind: "gaBind" }, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: GaEventFormInputDirective, decorators: [{ type: Directive, args: [{ selector: `input[gaEvent], select[gaEvent], textarea[gaEvent]` }] }], ctorParameters: function () { return [{ type: GaEventDirective, decorators: [{ type: Host }, { type: Optional }] }]; }, propDecorators: { gaBind: [{ type: Input }] } }); var GaActionEnum; (function (GaActionEnum) { GaActionEnum["ADD_PAYMENT_INFO"] = "add_payment_info"; GaActionEnum["ADD_TO_CART"] = "add_to_cart"; GaActionEnum["ADD_TO_WISHLIST"] = "add_to_wishlist"; GaActionEnum["BEGIN_CHECKOUT"] = "begin_checkout"; GaActionEnum["CHECKOUT_PROGRESS"] = "checkout_progress"; GaActionEnum["GENERATE_LEAD"] = "generate_lead"; GaActionEnum["LOGIN"] = "login"; GaActionEnum["PURCHASE"] = "purchase"; GaActionEnum["REFUND"] = "refund"; GaActionEnum["REMOVE_FROM_CART"] = "remove_from_cart"; GaActionEnum["SEARCH"] = "search"; GaActionEnum["SELECT_CONTENT"] = "select_content"; GaActionEnum["SET_CHECKOUT_OPTION"] = "set_checkout_option"; GaActionEnum["SHARE"] = "share"; GaActionEnum["SIGN_UP"] = "sign_up"; GaActionEnum["VIEW_ITEM"] = "view_item"; GaActionEnum["VIEW_ITEM_LIST"] = "view_item_list"; GaActionEnum["VIEW_PROMOTION"] = "view_promotion"; GaActionEnum["VIEW_SEARCH_RESULT"] = "view_search_results"; GaActionEnum["VIEW_SEARCH_RESULTS"] = "view_search_results"; })(GaActionEnum || (GaActionEnum = {})); /** * Provide a DI Configuration to attach GA Initialization at Angular Startup Cycle. */ const NGX_GOOGLE_ANALYTICS_INITIALIZER_PROVIDER = { provide: APP_INITIALIZER, multi: true, useFactory: GoogleAnalyticsInitializer, deps: [ NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN, NGX_GTAG_FN, DOCUMENT ] }; /** * Create a script element on DOM and link it to Google Analytics tracking code URI. * After that, execute exactly same init process as tracking snippet code. */ function GoogleAnalyticsInitializer(settings, gtag, document) { return () => __awaiter(this, void 0, void 0, function* () { var _a; if (!settings.trackingCode) { if (!isDevMode()) { console.error('Empty tracking code for Google Analytics. Make sure to provide one when initializing NgxGoogleAnalyticsModule.'); } return; } if (!gtag) { if (!isDevMode()) { console.error('Was not possible create or read gtag() fn. Make sure this module is running on a Browser w/ access to Window interface.'); } return; } if (!document) { if (!isDevMode()) { console.error('Was not possible to access Document interface. Make sure this module is running on a Browser w/ access do Document interface.'); } } // Set default ga.js uri settings.uri = settings.uri || `https://www.googletagmanager.com/gtag/js?id=${settings.trackingCode}`; // these commands should run first! settings.initCommands = (_a = settings === null || settings === void 0 ? void 0 : settings.initCommands) !== null && _a !== void 0 ? _a : []; // assert config command if (!settings.initCommands.find(x => x.command === 'config')) { settings.initCommands.unshift({ command: 'config', values: [settings.trackingCode] }); } // assert js command if (!settings.initCommands.find(x => x.command === 'js')) { settings.initCommands.unshift({ command: 'js', values: [new Date()] }); } for (const command of settings.initCommands) { gtag(command.command, ...command.values); } const s = document.createElement('script'); s.async = true; s.src = settings.uri; if (settings.nonce) { s.setAttribute('nonce', settings.nonce); } const head = document.getElementsByTagName('head')[0]; head.appendChild(s); }); } /** * Provide a Injection Token to global settings. */ const NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN = new InjectionToken('ngx-google-analytics-routing-settings', { factory: () => ({}) }); /** * Provide a DI Configuration to attach GA Trigger to Router Events at Angular Startup Cycle. */ const NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER = { provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: GoogleAnalyticsRouterInitializer, deps: [ NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN, GoogleAnalyticsService ] }; /** * Attach a listener to `NavigationEnd` Router event. So, every time Router finish the page resolution it should call `NavigationEnd` event. * We assume that NavigationEnd is the final page resolution and call GA `page_view` command. * * To avoid double binds, we also destroy the subscription when de Bootstrap Component is destroied. But, we don't know for sure * that this strategy does not cause double bind on multiple bootstrap components. * * We are using de component's injector reference to resolve Router, sou I hope there is no problem w/ double bing. * * If you have this problem, I encourage not Use NgxGoogleAnalyticsRouterModule and atach the listener on AppComponent initialization. */ function GoogleAnalyticsRouterInitializer(settings, gaService) { return (c) => __awaiter(this, void 0, void 0, function* () { const router = c.injector.get(Router); const { include = [], exclude = [] } = settings !== null && settings !== void 0 ? settings : {}; const includeRules = normalizePathRules(include); const excludeRules = normalizePathRules(exclude); const subs = router .events .pipe(filter((event) => event instanceof NavigationEnd), skip(1), // Prevend double views on the first tigger (because GA Already send one ping on setup) filter(event => includeRules.length > 0 ? includeRules.some(rule => rule.test(event.urlAfterRedirects)) : true), filter(event => excludeRules.length > 0 ? !excludeRules.some(rule => rule.test(event.urlAfterRedirects)) : true)) .subscribe(event => gaService.pageView(event.urlAfterRedirects, undefined)); // Cleanup c.onDestroy(() => subs.unsubscribe()); }); } /** Converts all path rules from string to Regex instances */ function normalizePathRules(rules) { return rules.map(rule => (rule instanceof RegExp) ? rule : new RegExp(`^${rule.replace('*', '.*')}$`, 'i')); } /** * Install Google Analytics Tracking code on your environment and configure tracking ID. * * This module should be a dependency on the highest level module of the application, i.e. AppModule in most use cases. */ class NgxGoogleAnalyticsModule { /** * You should provide a valid Google TrackingCode. This code will be provided to the entire application by * `NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN` token. You can inject this code in you components if you like by * use the following injection code `@Inject(NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN) gaConfig: IGoogleAnalyticsSettings` * * @param trackingCode The Google Tracking Code * @param initCommands When placed, it will run any GA Commands in sequence after setup GA environment. * @param uri When placed, it will change the default js URI to the provided one. * @param enableTracing When true, trace GA tracking errors on production mode. * @param nonce When placed, nonce will be added to script tag. */ static forRoot(trackingCode, initCommands = [], uri, enableTracing, nonce) { return { ngModule: NgxGoogleAnalyticsModule, providers: [ { provide: NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN, useValue: { trackingCode, initCommands, uri, enableTracing, nonce } }, NGX_GOOGLE_ANALYTICS_INITIALIZER_PROVIDER, ] }; } } NgxGoogleAnalyticsModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NgxGoogleAnalyticsModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsModule, declarations: [GaEventDirective, GaEventCategoryDirective, GaEventFormInputDirective], exports: [GaEventDirective, GaEventCategoryDirective, GaEventFormInputDirective] }); NgxGoogleAnalyticsModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsModule }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsModule, decorators: [{ type: NgModule, args: [{ imports: [], declarations: [ GaEventDirective, GaEventCategoryDirective, GaEventFormInputDirective ], exports: [ GaEventDirective, GaEventCategoryDirective, GaEventFormInputDirective ] }] }] }); /** * Attach a listener to `NavigationEnd` Router event. So, every time Router finish the page resolution it should call `NavigationEnd` event. * We assume that NavigationEnd is the final page resolution and call GA `page_view` command. * * To avoid double binds, we also destroy the subscription when de Bootstrap Component is destroied. But, we don't know for sure * that this strategy does not cause double bind on multiple bootstrap components. * * We are using de component's injector reference to resolve Router, sou I hope there is no problem w/ double bing. * * If you have this problem, I encourage not Use NgxGoogleAnalyticsRouterModule and atach the listener on AppComponent initialization. * * This Module is just a sugar for: * ```typescript constructor(private router: Router) {} ... ngOnInit() { ... this.router .events .pipe(takeUntil(this.onDestroy$)) .subscribe(event => { if (event instanceof NavigationEnd) { gaService.pageView(event.urlAfterRedirects, undefined); } }); ``` */ class NgxGoogleAnalyticsRouterModule { static forRoot(settings) { return { ngModule: NgxGoogleAnalyticsRouterModule, providers: [ { provide: NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN, useValue: settings !== null && settings !== void 0 ? settings : {} } ] }; } } NgxGoogleAnalyticsRouterModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsRouterModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); NgxGoogleAnalyticsRouterModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsRouterModule, imports: [CommonModule, NgxGoogleAnalyticsModule] }); NgxGoogleAnalyticsRouterModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsRouterModule, providers: [ NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER ], imports: [CommonModule, NgxGoogleAnalyticsModule] }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.0.2", ngImport: i0, type: NgxGoogleAnalyticsRouterModule, decorators: [{ type: NgModule, args: [{ imports: [ CommonModule, NgxGoogleAnalyticsModule ], providers: [ NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER ], declarations: [] }] }] }); /* * Public API Surface of ngx-google-analytics */ /** * Generated bundle index. Do not edit. */ export { GaActionEnum, GaEventCategoryDirective, GaEventDirective, GaEventFormInputDirective, GoogleAnalyticsInitializer, GoogleAnalyticsRouterInitializer, GoogleAnalyticsService, NGX_DATA_LAYER, NGX_GOOGLE_ANALYTICS_INITIALIZER_PROVIDER, NGX_GOOGLE_ANALYTICS_ROUTER_INITIALIZER_PROVIDER, NGX_GOOGLE_ANALYTICS_ROUTING_SETTINGS_TOKEN, NGX_GOOGLE_ANALYTICS_SETTINGS_TOKEN, NGX_GTAG_FN, NGX_WINDOW, NgxGoogleAnalyticsModule, NgxGoogleAnalyticsRouterModule, getDataLayerFn, getGtagFn }; //# sourceMappingURL=ngx-google-analytics.mjs.map