Skip to content

Commit ece6711

Browse files
committed
feat(analytics): added google analytics service
1 parent 25ec23a commit ece6711

File tree

11 files changed

+214
-65
lines changed

11 files changed

+214
-65
lines changed

PLAYBOOK.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ Do-it-yourself step-by-step instructions to create this project structure from s
99
1010
| Software | Version | Optional |
1111
|-------------------------------|----------|----------|
12-
| Node | v10.11.0 | |
13-
| NPM | v6.4.0 | |
12+
| Node | v11.1.0 | |
13+
| NPM | v6.4.1 | |
1414
| Angular CLI | v7.0.0 | |
1515
| @nrwl/schematics | v7.0.0 | |
16-
| @nestjs/cli | v5.5.0 | |
16+
| @nestjs/cli | v5.6.2 | |
1717

1818
### Install Prerequisites
1919
```bash
@@ -243,6 +243,7 @@ ng g service services/ServiceWorker --project=core --dry-run
243243
ng g service services/MediaQuery --project=core --dry-run
244244
ng g service services/DeepLink --project=core --dry-run
245245
ng g service services/Feature --project=core --dry-run
246+
ng g service services/GoogleAnalytics --project=core --dry-run
246247

247248
# `material` module to encapulate material libs which is impoted into any `Lazy-loaded Feature Modules` that need material components
248249
ng g lib material --prefix=ngx --spec=false --tags=shared-module --unit-test-runner=jest --dry-run

apps/webapp/src/app/app.component.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2-
import { PageTitleService, ServiceWorkerService } from '@ngx-starter-kit/core';
2+
import { ServiceWorkerService } from '@ngx-starter-kit/core';
33
import { Meta, Title } from '@angular/platform-browser';
44

55
@Component({
@@ -9,8 +9,7 @@ import { Meta, Title } from '@angular/platform-browser';
99
encapsulation: ViewEncapsulation.None,
1010
})
1111
export class AppComponent implements OnInit {
12-
// HINT: keep PageTitleService injected here, so that, it get initialized during bootstrap
13-
constructor(private sw: ServiceWorkerService, public meta: Meta, private pageTitleService: PageTitleService) {
12+
constructor(private sw: ServiceWorkerService, public meta: Meta) {
1413
meta.addTags([
1514
{ charset: 'UTF-8' },
1615
{ name: 'description', content: 'NGX Starter Kit' },

apps/webapp/src/index.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
<script>
3131
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
3232
ga('create', 'UA-38731590-2', 'auto');
33-
ga('send', 'pageview');
3433
</script>
3534
<script async src='https://www.google-analytics.com/analytics.js'></script>
3635
</body>

libs/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export { MediaQueryService } from './lib/services/media-query.service';
55
export { DeepLinkService } from './lib/services/deep-link.service';
66
export { RouterStateData} from './lib/state/custom-router-state.serializer';
77
export { FeatureService, BrowserFeatureKey, BrowserFeature } from './lib/services/feature.service';
8+
export { GoogleAnalyticsService } from './lib/services/google-analytics.service';
89
export { WINDOW } from './lib/services/window.token';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { TestBed } from '@angular/core/testing';
2+
3+
import { GoogleAnalyticsService } from './google-analytics.service';
4+
5+
describe('GoogleAnalyticsService', () => {
6+
beforeEach(() => TestBed.configureTestingModule({}));
7+
8+
it('should be created', () => {
9+
const service: GoogleAnalyticsService = TestBed.get(GoogleAnalyticsService);
10+
expect(service).toBeTruthy();
11+
});
12+
});
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Injectable } from '@angular/core';
2+
import { NavigationEnd, Router, RouterEvent } from '@angular/router';
3+
import { distinctUntilChanged, filter, tap } from 'rxjs/operators';
4+
5+
export enum EventCategory {
6+
SideNav = 'sideNav',
7+
Outbound = 'outboundLink',
8+
Login = 'login',
9+
}
10+
11+
export enum EventAction {
12+
Play = 'play',
13+
Click = 'click',
14+
}
15+
16+
/**
17+
* EventBus will use this service
18+
*/
19+
@Injectable({
20+
providedIn: 'root',
21+
})
22+
export class GoogleAnalyticsService {
23+
constructor(public router: Router) {}
24+
25+
public initPageViewTracking() {
26+
this.router.events
27+
.pipe(
28+
filter(event => event instanceof NavigationEnd),
29+
distinctUntilChanged((previous: any, current: RouterEvent) => {
30+
return previous.url === current.url;
31+
}),
32+
)
33+
.subscribe((event: NavigationEnd) => {
34+
this.setPage(event.urlAfterRedirects);
35+
});
36+
}
37+
38+
/**
39+
* set user after login success.
40+
* @param userId
41+
*/
42+
public setUsername(userId: string) {
43+
if (typeof ga === 'function') {
44+
ga('set', 'userId', userId);
45+
}
46+
}
47+
48+
/**
49+
* set page after navigation success
50+
* @param path
51+
*/
52+
public setPage(path: string) {
53+
if (typeof ga === 'function') {
54+
// TODO: remove dynamic data if needed. e.g., /user/USER_ID/profile
55+
ga('set', 'page', path);
56+
ga('send', 'pageview');
57+
}
58+
}
59+
60+
/**
61+
* @param eventCategory : 'Video'
62+
* @param eventAction : 'play'
63+
* @param eventLabel : 'Fall Campaign'
64+
* @param eventValue : 42
65+
*/
66+
public emitEvent(eventCategory: EventCategory, eventAction: string, eventLabel?: string, eventValue?: number) {
67+
if (typeof ga === 'function') {
68+
ga('send', 'event', {
69+
eventCategory: eventCategory,
70+
eventAction: eventAction,
71+
eventLabel: eventLabel,
72+
eventValue: eventValue,
73+
});
74+
}
75+
}
76+
77+
/**
78+
* @param socialNetwork : 'facebook
79+
* @param socialAction : 'like'
80+
* @param socialTarget : 'http://foo.com'
81+
*/
82+
public emitSocial(socialNetwork: string, socialAction: string, socialTarget: string) {
83+
if (typeof ga === 'function') {
84+
ga('send', 'social', {
85+
socialNetwork,
86+
socialAction,
87+
socialTarget,
88+
});
89+
}
90+
}
91+
92+
/**
93+
* @param timingCategory : 'JS Dependencies'
94+
* @param timingVar : 'load'
95+
* @param timingValue: 20 in milliseconds
96+
* @param timingLabel : 'Google CDN'
97+
*/
98+
99+
public emitTiming(timingCategory: string, timingVar: string, timingValue: number, timingLabel?: string) {
100+
if (typeof ga === 'function') {
101+
ga('send', 'timing', {
102+
timingCategory,
103+
timingVar,
104+
timingValue,
105+
timingLabel,
106+
});
107+
}
108+
}
109+
110+
public emitException(exDescription?: string, exFatal?: boolean) {
111+
if (typeof ga === 'function') {
112+
ga('send', 'exception', {
113+
exDescription,
114+
exFatal,
115+
});
116+
}
117+
}
118+
}
Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { Injectable } from '@angular/core';
22
import { Title } from '@angular/platform-browser';
33
import { Store } from '@ngxs/store';
4-
import { RouterState } from '@ngxs/router-plugin';
5-
import { RouterStateData } from '../state/custom-router-state.serializer';
64

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

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

20-
// Automatically set pageTitle from router state data
21-
store.select<any>(RouterState.state).subscribe((routerStateData: RouterStateData) => {
22-
bodyTitle.setTitle(
23-
`${Array.from(routerStateData.breadcrumbs.keys())
24-
.reverse()
25-
.join(' | ')} | ${this.defaultTitle}`,
26-
);
27-
28-
ga('send', 'pageview', routerStateData.url);
29-
});
18+
public setTitle(breadcrumbs: Map<string, string>) {
19+
this.bodyTitle.setTitle(
20+
`${Array.from(breadcrumbs.keys())
21+
.reverse()
22+
.join(' | ')} | ${this.defaultTitle}`,
23+
);
3024
}
3125
}

libs/core/src/lib/state/eventbus.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
1-
import { Actions, ofActionSuccessful, ofActionErrored, ofActionDispatched, Store } from '@ngxs/store';
1+
import { Actions, ofActionErrored, ofActionSuccessful, Store } from '@ngxs/store';
22
import { Injectable } from '@angular/core';
3-
import { Login } from '@ngx-starter-kit/auth';
3+
import { Login, LoginSuccess } from '@ngx-starter-kit/auth';
44
import {
5+
AuthenticateWebSocket,
56
ConnectWebSocket,
67
DisconnectWebSocket,
7-
WebSocketDisconnected,
88
WebSocketConnected,
9-
AuthenticateWebSocket,
9+
WebSocketDisconnected,
1010
} from '@ngx-starter-kit/socketio-plugin';
11+
import { RouterNavigation } from '@ngxs/router-plugin';
12+
import { RouterStateData } from '@ngx-starter-kit/core';
13+
import { distinctUntilChanged, map } from 'rxjs/operators';
14+
import { PageTitleService } from '../services/page-title.service';
15+
import { GoogleAnalyticsService } from '../services/google-analytics.service';
1116

1217
@Injectable({
1318
providedIn: 'root',
1419
})
1520
export class EventBus {
16-
constructor(private actions$: Actions, private store: Store) {
17-
this.actions$.pipe(ofActionDispatched(Login)).subscribe(action => console.log('Login.......Action Dispatched'));
21+
constructor(
22+
private actions$: Actions,
23+
private store: Store,
24+
private analytics: GoogleAnalyticsService,
25+
private pageTitle: PageTitleService,
26+
) {
1827
this.actions$.pipe(ofActionSuccessful(Login)).subscribe(action => console.log('Login........Action Successful'));
1928
this.actions$.pipe(ofActionErrored(Login)).subscribe(action => console.log('Login........Action Errored'));
29+
this.actions$
30+
.pipe(ofActionSuccessful(LoginSuccess))
31+
.subscribe((action: LoginSuccess) => {
32+
console.log('LoginSuccess........Action Successful', action.payload.preferred_username);
33+
this.analytics.setUsername(action.payload.preferred_username);
34+
});
35+
2036
this.actions$
2137
.pipe(ofActionSuccessful(ConnectWebSocket))
2238
.subscribe(action => console.log('ConnectWebSocket........Action Successful'));
@@ -30,5 +46,19 @@ export class EventBus {
3046
this.actions$
3147
.pipe(ofActionSuccessful(WebSocketDisconnected))
3248
.subscribe(action => console.log('WebSocketDisconnected........Action Successful'));
49+
50+
this.actions$
51+
.pipe(
52+
ofActionSuccessful(RouterNavigation),
53+
map((action: RouterNavigation) => action.routerState as any),
54+
distinctUntilChanged((previous: RouterStateData, current: RouterStateData) => {
55+
return previous.url === current.url;
56+
}),
57+
)
58+
.subscribe(data => {
59+
console.log('ofActionSuccessful RouterNavigation', data.url);
60+
this.pageTitle.setTitle(data.breadcrumbs);
61+
this.analytics.setPage(data.url);
62+
});
3363
}
3464
}

0 commit comments

Comments
 (0)