ngx-hotjar
Version:
A simple ng wrapper to load hotjar dependency by angular way
340 lines (328 loc) • 11 kB
JavaScript
import { __awaiter } from 'tslib';
import { InjectionToken, inject, PLATFORM_ID, isDevMode, ɵɵdefineInjectable, ɵɵinject, Injectable, Inject, APP_BOOTSTRAP_LISTENER, APP_INITIALIZER, NgModule } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { isPlatformBrowser, DOCUMENT, CommonModule } from '@angular/common';
/**
* Check if this environment can access Window object and return window or null if false.
*/
function getWindow(platformId) {
return (isPlatformBrowser(platformId))
? window : null;
}
/**
* Provide DOM Window reference or null if the environment is not a Browser.
*/
const WINDOW = new InjectionToken('hj-window', {
factory: () => getWindow(inject(PLATFORM_ID)),
});
/**
* Check if there is some global function called gtag on Window object, or create an empty function to doesn't brake codes...
*/
function getHjFn(window) {
// // h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
return (window) ? window.hj : null;
}
/**
* Provides an injection token to access Google Analytics Gtag Function
*/
const NGX_HJ_FN = new InjectionToken('ngx-hj-fn', {
providedIn: 'root',
factory: () => getHjFn(inject(WINDOW))
});
/**
* Provides a token to override default settings. You can use this token to enhance our library
* and configure multiple sites/app on the same environment.
*/
const NGX_HOTJAR_SETTINGS_TOKEN = new InjectionToken('ngx-hotjar-settings', {
factory: () => ({ trackingCode: '', version: 6, ennableTracing: false })
});
class NgxHotjarService {
/** @ignore */
constructor(
/** @ignore */
_hj,
/** @ignore */
settings) {
this._hj = _hj;
this.settings = settings;
}
/**
* Provide direct access to the `hj.*` static functions. If the desired function is not available on type definition, you can cast to `any` as following.
*
```typescript
(hjService.lib as any).myBrandNewStaticFn()
```
*/
get lib() {
return this._hj;
}
/** Expose Hotjar Function calls */
hj(...args) {
try {
this._hj(...args);
}
catch (err) {
if (isDevMode() || this.settings.ennableTracing) {
console.error(err.message);
}
}
}
/**
* Fires an PageView event on Hotjar. Use this method to trigger an virtual url path. The same as
*
```typescript
hj('vpv', path)
```
*/
virtualPageView(path) {
this.hj('vpv', path);
}
/**
* Fires an event on Hotjar. Use this method to trigger events on forms and start video recordings. Same as
*
```typescript
hj('trigger', path)
```
*/
trigger(path) {
this.hj('trigger', path);
}
tagRecording(tagOrCollection, ...tags) {
// Retrocompatibility
if (!Array.isArray(tagOrCollection)) {
tagOrCollection = [tagOrCollection];
}
this.hj('tagRecording', tagOrCollection.concat(...tags));
}
/**
* This option is available in case you need to set up page change tracking manually
* within your app's router.
*
```typescript
hj('stateChange', path)
```
*/
stateChange(path) {
this.hj('stateChange', path);
}
/**
* Signals form submission success
*
```typescript
hj('formSubmitSuccessful');
```
*/
formSubmitSuccessful() {
this.hj('formSubmitSuccessful');
}
/**
* Signals form submission failure
*
```typescript
hj('formSubmitFailed');
```
*/
formSubmitFailed() {
this.hj('formSubmitFailed');
}
}
NgxHotjarService.ɵprov = ɵɵdefineInjectable({ factory: function NgxHotjarService_Factory() { return new NgxHotjarService(ɵɵinject(NGX_HJ_FN), ɵɵinject(NGX_HOTJAR_SETTINGS_TOKEN)); }, token: NgxHotjarService, providedIn: "root" });
NgxHotjarService.decorators = [
{ type: Injectable, args: [{
providedIn: 'root'
},] }
];
NgxHotjarService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [NGX_HJ_FN,] }] },
{ type: undefined, decorators: [{ type: Inject, args: [NGX_HOTJAR_SETTINGS_TOKEN,] }] }
];
/**
* Provide a DI Configuration to attach Hotjar Trigger to Router Events at Angular Startup Cycle.
*/
const NGX_HOTJAR_ROUTER_INITIALIZER_PROVIDER = {
provide: APP_BOOTSTRAP_LISTENER,
multi: true,
useFactory: HotjarRouterInitializer,
deps: [
NgxHotjarService
]
};
/**
* 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 Hotjar `stateChange` 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 NgxHotjarRouterModule and atach the listener on AppComponent initialization.
*/
function HotjarRouterInitializer(hjService) {
return (c) => __awaiter(this, void 0, void 0, function* () {
const router = c.injector.get(Router);
const subs = router
.events
.subscribe(event => {
if (event instanceof NavigationEnd) {
hjService.stateChange(event.urlAfterRedirects);
}
});
// Cleanup
c.onDestroy(() => subs.unsubscribe());
});
}
/**
* Provides a TOKEN to manually configure Hojtar tracking code by angular way.
*/
const NGX_HOTJAR_INITIALIZER_PROVIDER = {
provide: APP_INITIALIZER,
multi: true,
useFactory: hotjarInitializer,
deps: [
NGX_HOTJAR_SETTINGS_TOKEN,
DOCUMENT,
WINDOW
]
};
/**
* Configuration Factory to create hotjar install script tag and attache on DOM at angular initialization.
*/
function hotjarInitializer(settings, document, window) {
return () => __awaiter(this, void 0, void 0, function* () {
if (!settings.trackingCode) {
if (isDevMode()) {
console.error('Empty tracking code for Hotjar. Make sure to provide one when initializing NgxHotjarModule.');
}
return;
}
if (!document) {
if (isDevMode()) {
console.error('Was not possible to access `document` instance. Make shure this environment works on a Broser like API');
}
return;
}
if (!window) {
if (isDevMode()) {
console.error('Was not possible to access `window` api. Make sure this environment works like a browser.');
}
return;
}
Object.defineProperty(window, 'hj', {
value: (window.hj || function () {
(window.hj.q = window.hj.q || []).push(arguments);
}),
configurable: true,
writable: true
});
Object.defineProperty(window, '_hjSettings', {
value: { hjid: settings.trackingCode, hjsv: (settings.version || 6) },
configurable: true,
writable: true
});
const head = document.querySelector('head'), script = document.createElement('script'), uri = `https://static.hotjar.com/c/hotjar-${window._hjSettings.hjid}.js?sv=${window._hjSettings.hjsv}`;
script.async = true;
script.src = (settings.uri || uri);
head.appendChild(script);
});
}
/**
* Install Hotjar scripts on Angular Startup life cycle if this environment is a Browser, otherwise just ignore this step.
*
* You shall add this module on the Gighest level module of your application, aka `AppModule`. When
* setup this module, you also have to provide you hotjat tracking code and the version of script. The default version
* is 6. We do not recoment to expose the tracking code in the repository, so please, use angular environment variable.
*
* ## Exemple of Use
*
```typescript
\@NgModule({
...
imports: [
...
NgxHotjarModule.forRoot(envorinment.hj)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
]
})
```
*/
class NgxHotjarModule {
/**
* Setup global settings and provide Hotjar Services.
*
* You private tracking code. This tracking code is also known as the same number as `SITE ID` inside Hotjar Dashboard.
*/
static forRoot(trackingCode, version = 6, uri) {
return {
ngModule: NgxHotjarModule,
providers: [
{
provide: NGX_HOTJAR_SETTINGS_TOKEN,
useValue: {
trackingCode,
version,
uri
}
},
NGX_HOTJAR_INITIALIZER_PROVIDER
]
};
}
}
NgxHotjarModule.decorators = [
{ type: NgModule, args: [{
imports: [],
declarations: [],
exports: []
},] }
];
/**
* 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 Hotjar `stateChange` 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 NgxHotjarRouterModule 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) {
hjService.pageView(event.urlAfterRedirects, undefined);
}
});
```
*/
class NgxHotjarRouterModule {
}
NgxHotjarRouterModule.decorators = [
{ type: NgModule, args: [{
imports: [
CommonModule,
NgxHotjarModule
],
providers: [
NGX_HOTJAR_ROUTER_INITIALIZER_PROVIDER
],
declarations: []
},] }
];
/*
* Public API Surface of ngx-hotjar
*/
/**
* Generated bundle index. Do not edit.
*/
export { HotjarRouterInitializer, NGX_HJ_FN, NGX_HOTJAR_INITIALIZER_PROVIDER, NGX_HOTJAR_ROUTER_INITIALIZER_PROVIDER, NGX_HOTJAR_SETTINGS_TOKEN, NgxHotjarModule, NgxHotjarRouterModule, NgxHotjarService, WINDOW, getHjFn, getWindow, hotjarInitializer };
//# sourceMappingURL=ngx-hotjar.js.map