Skip to content

Commit

Permalink
feat(analytics): added google analytics service
Browse files Browse the repository at this point in the history
  • Loading branch information
xmlking committed Nov 11, 2018
1 parent 25ec23a commit ece6711
Show file tree
Hide file tree
Showing 11 changed files with 214 additions and 65 deletions.
7 changes: 4 additions & 3 deletions PLAYBOOK.md
Expand Up @@ -9,11 +9,11 @@ Do-it-yourself step-by-step instructions to create this project structure from s
| Software | Version | Optional |
|-------------------------------|----------|----------|
| Node | v10.11.0 | |
| NPM | v6.4.0 | |
| Node | v11.1.0 | |
| NPM | v6.4.1 | |
| Angular CLI | v7.0.0 | |
| @nrwl/schematics | v7.0.0 | |
| @nestjs/cli | v5.5.0 | |
| @nestjs/cli | v5.6.2 | |

### Install Prerequisites
```bash
Expand Down Expand Up @@ -243,6 +243,7 @@ ng g service services/ServiceWorker --project=core --dry-run
ng g service services/MediaQuery --project=core --dry-run
ng g service services/DeepLink --project=core --dry-run
ng g service services/Feature --project=core --dry-run
ng g service services/GoogleAnalytics --project=core --dry-run

# `material` module to encapulate material libs which is impoted into any `Lazy-loaded Feature Modules` that need material components
ng g lib material --prefix=ngx --spec=false --tags=shared-module --unit-test-runner=jest --dry-run
Expand Down
5 changes: 2 additions & 3 deletions apps/webapp/src/app/app.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { PageTitleService, ServiceWorkerService } from '@ngx-starter-kit/core';
import { ServiceWorkerService } from '@ngx-starter-kit/core';
import { Meta, Title } from '@angular/platform-browser';

@Component({
Expand All @@ -9,8 +9,7 @@ import { Meta, Title } from '@angular/platform-browser';
encapsulation: ViewEncapsulation.None,
})
export class AppComponent implements OnInit {
// HINT: keep PageTitleService injected here, so that, it get initialized during bootstrap
constructor(private sw: ServiceWorkerService, public meta: Meta, private pageTitleService: PageTitleService) {
constructor(private sw: ServiceWorkerService, public meta: Meta) {
meta.addTags([
{ charset: 'UTF-8' },
{ name: 'description', content: 'NGX Starter Kit' },
Expand Down
1 change: 0 additions & 1 deletion apps/webapp/src/index.html
Expand Up @@ -30,7 +30,6 @@
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', 'UA-38731590-2', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
</body>
Expand Down
1 change: 1 addition & 0 deletions libs/core/src/index.ts
Expand Up @@ -5,4 +5,5 @@ export { MediaQueryService } from './lib/services/media-query.service';
export { DeepLinkService } from './lib/services/deep-link.service';
export { RouterStateData} from './lib/state/custom-router-state.serializer';
export { FeatureService, BrowserFeatureKey, BrowserFeature } from './lib/services/feature.service';
export { GoogleAnalyticsService } from './lib/services/google-analytics.service';
export { WINDOW } from './lib/services/window.token';
12 changes: 12 additions & 0 deletions libs/core/src/lib/services/google-analytics.service.spec.ts
@@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';

import { GoogleAnalyticsService } from './google-analytics.service';

describe('GoogleAnalyticsService', () => {
beforeEach(() => TestBed.configureTestingModule({}));

it('should be created', () => {
const service: GoogleAnalyticsService = TestBed.get(GoogleAnalyticsService);
expect(service).toBeTruthy();
});
});
118 changes: 118 additions & 0 deletions libs/core/src/lib/services/google-analytics.service.ts
@@ -0,0 +1,118 @@
import { Injectable } from '@angular/core';
import { NavigationEnd, Router, RouterEvent } from '@angular/router';
import { distinctUntilChanged, filter, tap } from 'rxjs/operators';

export enum EventCategory {
SideNav = 'sideNav',
Outbound = 'outboundLink',
Login = 'login',
}

export enum EventAction {
Play = 'play',
Click = 'click',
}

/**
* EventBus will use this service
*/
@Injectable({
providedIn: 'root',
})
export class GoogleAnalyticsService {
constructor(public router: Router) {}

public initPageViewTracking() {
this.router.events
.pipe(
filter(event => event instanceof NavigationEnd),
distinctUntilChanged((previous: any, current: RouterEvent) => {
return previous.url === current.url;
}),
)
.subscribe((event: NavigationEnd) => {
this.setPage(event.urlAfterRedirects);
});
}

/**
* set user after login success.
* @param userId
*/
public setUsername(userId: string) {
if (typeof ga === 'function') {
ga('set', 'userId', userId);
}
}

/**
* set page after navigation success
* @param path
*/
public setPage(path: string) {
if (typeof ga === 'function') {
// TODO: remove dynamic data if needed. e.g., /user/USER_ID/profile
ga('set', 'page', path);
ga('send', 'pageview');
}
}

/**
* @param eventCategory : 'Video'
* @param eventAction : 'play'
* @param eventLabel : 'Fall Campaign'
* @param eventValue : 42
*/
public emitEvent(eventCategory: EventCategory, eventAction: string, eventLabel?: string, eventValue?: number) {
if (typeof ga === 'function') {
ga('send', 'event', {
eventCategory: eventCategory,
eventAction: eventAction,
eventLabel: eventLabel,
eventValue: eventValue,
});
}
}

/**
* @param socialNetwork : 'facebook
* @param socialAction : 'like'
* @param socialTarget : 'http://foo.com'
*/
public emitSocial(socialNetwork: string, socialAction: string, socialTarget: string) {
if (typeof ga === 'function') {
ga('send', 'social', {
socialNetwork,
socialAction,
socialTarget,
});
}
}

/**
* @param timingCategory : 'JS Dependencies'
* @param timingVar : 'load'
* @param timingValue: 20 in milliseconds
* @param timingLabel : 'Google CDN'
*/

public emitTiming(timingCategory: string, timingVar: string, timingValue: number, timingLabel?: string) {
if (typeof ga === 'function') {
ga('send', 'timing', {
timingCategory,
timingVar,
timingValue,
timingLabel,
});
}
}

public emitException(exDescription?: string, exFatal?: boolean) {
if (typeof ga === 'function') {
ga('send', 'exception', {
exDescription,
exFatal,
});
}
}
}
22 changes: 8 additions & 14 deletions libs/core/src/lib/services/page-title.service.ts
@@ -1,12 +1,9 @@
import { Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { Store } from '@ngxs/store';
import { RouterState } from '@ngxs/router-plugin';
import { RouterStateData } from '../state/custom-router-state.serializer';

declare var ga: any;
/**
* Service responsible for setting the title that appears above the home and dashboard pages.
* EventBus will use this service
*/
@Injectable({
providedIn: 'root',
Expand All @@ -16,16 +13,13 @@ export class PageTitleService {

constructor(private store: Store, private bodyTitle: Title) {
this.defaultTitle = bodyTitle.getTitle() || 'WebApp';
}

// Automatically set pageTitle from router state data
store.select<any>(RouterState.state).subscribe((routerStateData: RouterStateData) => {
bodyTitle.setTitle(
`${Array.from(routerStateData.breadcrumbs.keys())
.reverse()
.join(' | ')} | ${this.defaultTitle}`,
);

ga('send', 'pageview', routerStateData.url);
});
public setTitle(breadcrumbs: Map<string, string>) {
this.bodyTitle.setTitle(
`${Array.from(breadcrumbs.keys())
.reverse()
.join(' | ')} | ${this.defaultTitle}`,
);
}
}
42 changes: 36 additions & 6 deletions libs/core/src/lib/state/eventbus.ts
@@ -1,22 +1,38 @@
import { Actions, ofActionSuccessful, ofActionErrored, ofActionDispatched, Store } from '@ngxs/store';
import { Actions, ofActionErrored, ofActionSuccessful, Store } from '@ngxs/store';
import { Injectable } from '@angular/core';
import { Login } from '@ngx-starter-kit/auth';
import { Login, LoginSuccess } from '@ngx-starter-kit/auth';
import {
AuthenticateWebSocket,
ConnectWebSocket,
DisconnectWebSocket,
WebSocketDisconnected,
WebSocketConnected,
AuthenticateWebSocket,
WebSocketDisconnected,
} from '@ngx-starter-kit/socketio-plugin';
import { RouterNavigation } from '@ngxs/router-plugin';
import { RouterStateData } from '@ngx-starter-kit/core';
import { distinctUntilChanged, map } from 'rxjs/operators';
import { PageTitleService } from '../services/page-title.service';
import { GoogleAnalyticsService } from '../services/google-analytics.service';

@Injectable({
providedIn: 'root',
})
export class EventBus {
constructor(private actions$: Actions, private store: Store) {
this.actions$.pipe(ofActionDispatched(Login)).subscribe(action => console.log('Login.......Action Dispatched'));
constructor(
private actions$: Actions,
private store: Store,
private analytics: GoogleAnalyticsService,
private pageTitle: PageTitleService,
) {
this.actions$.pipe(ofActionSuccessful(Login)).subscribe(action => console.log('Login........Action Successful'));
this.actions$.pipe(ofActionErrored(Login)).subscribe(action => console.log('Login........Action Errored'));
this.actions$
.pipe(ofActionSuccessful(LoginSuccess))
.subscribe((action: LoginSuccess) => {
console.log('LoginSuccess........Action Successful', action.payload.preferred_username);
this.analytics.setUsername(action.payload.preferred_username);
});

this.actions$
.pipe(ofActionSuccessful(ConnectWebSocket))
.subscribe(action => console.log('ConnectWebSocket........Action Successful'));
Expand All @@ -30,5 +46,19 @@ export class EventBus {
this.actions$
.pipe(ofActionSuccessful(WebSocketDisconnected))
.subscribe(action => console.log('WebSocketDisconnected........Action Successful'));

this.actions$
.pipe(
ofActionSuccessful(RouterNavigation),
map((action: RouterNavigation) => action.routerState as any),
distinctUntilChanged((previous: RouterStateData, current: RouterStateData) => {
return previous.url === current.url;
}),
)
.subscribe(data => {
console.log('ofActionSuccessful RouterNavigation', data.url);
this.pageTitle.setTitle(data.breadcrumbs);
this.analytics.setPage(data.url);
});
}
}

0 comments on commit ece6711

Please sign in to comment.