Skip to content

Commit d47dc3f

Browse files
committed
Fix proxy in Node, get component name for analytics in both JIT and AOT
1 parent 7fe92ed commit d47dc3f

File tree

5 files changed

+73
-54
lines changed

5 files changed

+73
-54
lines changed

src/analytics/analytics.service.ts

+36-14
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Injectable, Optional, NgZone, OnDestroy, ComponentFactoryResolver, Inject, PLATFORM_ID } from '@angular/core';
1+
import { Injectable, Optional, NgZone, OnDestroy, ComponentFactoryResolver, Inject, PLATFORM_ID, Injector, NgModuleFactory } from '@angular/core';
22
import { Subscription, from, Observable, empty, of } from 'rxjs';
33
import { filter, withLatestFrom, switchMap, map, tap, pairwise, startWith, groupBy, mergeMap } from 'rxjs/operators';
4-
import { Router, NavigationEnd, ActivationEnd } from '@angular/router';
4+
import { Router, NavigationEnd, ActivationEnd, ROUTES } from '@angular/router';
55
import { runOutsideAngular } from '@angular/fire';
6-
import { AngularFireAnalytics } from './analytics';
6+
import { AngularFireAnalytics, DEBUG_MODE } from './analytics';
77
import { User } from 'firebase/app';
88
import { Title } from '@angular/platform-browser';
99
import { isPlatformBrowser } from '@angular/common';
@@ -27,6 +27,8 @@ const DEFAULT_SCREEN_CLASS = '???';
2727
const NG_PRIMARY_OUTLET = 'primary';
2828
const SCREEN_INSTANCE_DELIMITER = '#';
2929

30+
const ANNOTATIONS = '__annotations__';
31+
3032
@Injectable()
3133
export class ScreenTrackingService implements OnDestroy {
3234

@@ -38,7 +40,9 @@ export class ScreenTrackingService implements OnDestroy {
3840
@Optional() title:Title,
3941
componentFactoryResolver: ComponentFactoryResolver,
4042
@Inject(PLATFORM_ID) platformId:Object,
41-
zone: NgZone
43+
@Optional() @Inject(DEBUG_MODE) debugModeEnabled:boolean|null,
44+
zone: NgZone,
45+
injector: Injector
4246
) {
4347
if (!router || !isPlatformBrowser(platformId)) { return this }
4448
zone.runOutsideAngular(() => {
@@ -70,23 +74,40 @@ export class ScreenTrackingService implements OnDestroy {
7074
// it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style
7175
return of({...params, [SCREEN_CLASS_KEY]: loadChildren.split('#')[1]});
7276
} else if (typeof component === 'string') {
73-
// TODO figure out when this would this be a string
7477
return of({...params, [SCREEN_CLASS_KEY]: component });
7578
} else if (component) {
7679
const componentFactory = componentFactoryResolver.resolveComponentFactory(component);
7780
return of({...params, [SCREEN_CLASS_KEY]: componentFactory.selector });
7881
} else if (loadChildren) {
7982
const loadedChildren = loadChildren();
80-
var loadedChildren$: Observable<any>;
81-
// TODO clean up this handling...
82-
// can componentFactorymoduleType take an ngmodulefactory or should i pass moduletype?
83-
try { loadedChildren$ = from(zone.runOutsideAngular(() => loadedChildren as any)) } catch(_) { loadedChildren$ = of(loadedChildren as any) }
84-
return loadedChildren$.pipe(map(child => {
85-
const componentFactory = componentFactoryResolver.resolveComponentFactory(child);
86-
return {...params, [SCREEN_CLASS_KEY]: componentFactory.selector };
87-
}));
83+
var loadedChildren$: Observable<any> = (loadedChildren instanceof Observable) ? loadedChildren : from(Promise.resolve(loadedChildren));
84+
return loadedChildren$.pipe(
85+
map(lazyModule => {
86+
if (lazyModule instanceof NgModuleFactory) {
87+
// AOT create an injector
88+
const moduleRef = lazyModule.create(injector);
89+
// INVESTIGATE is this the right way to get at the matching route?
90+
const routes = moduleRef.injector.get(ROUTES);
91+
const component = routes[0][0].component; // should i just be grabbing 0-0 here?
92+
try {
93+
const componentFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(component!);
94+
return {...params, [SCREEN_CLASS_KEY]: componentFactory.selector};
95+
} catch(_) {
96+
return {...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS};
97+
}
98+
} else {
99+
// JIT look at the annotations
100+
// INVESTIGATE are there public APIs for this stuff?
101+
const declarations = [].concat.apply([], (lazyModule[ANNOTATIONS] || []).map((f:any) => f.declarations));
102+
const selectors = [].concat.apply([], declarations.map((c:any) => (c[ANNOTATIONS] || []).map((f:any) => f.selector)));
103+
// should I just be grabbing the selector like this or should i match against the route component?
104+
// const routerModule = lazyModule.ngInjectorDef.imports.find(i => !!i.ngModule);
105+
// const route = routerModule.providers[0].find(p => p.provide == ROUTES).useValue[0];
106+
return {...params, [SCREEN_CLASS_KEY]: selectors[0] || DEFAULT_SCREEN_CLASS};
107+
}
108+
})
109+
);
88110
} else {
89-
// TODO figure out what forms of router events I might be missing
90111
return of({...params, [SCREEN_CLASS_KEY]: DEFAULT_SCREEN_CLASS});
91112
}
92113
}),
@@ -116,6 +137,7 @@ export class ScreenTrackingService implements OnDestroy {
116137
[FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY]: prior[FIREBASE_SCREEN_INSTANCE_ID_KEY],
117138
...current!
118139
} : current!),
140+
tap(params => debugModeEnabled && console.info(SCREEN_VIEW_EVENT, params)),
119141
tap(params => zone.runOutsideAngular(() => analytics.logEvent(SCREEN_VIEW_EVENT, params)))
120142
).subscribe();
121143
});

src/analytics/analytics.spec.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ReflectiveInjector, Provider } from '@angular/core';
22
import { TestBed, inject } from '@angular/core/testing';
33
import { FirebaseApp, FirebaseOptionsToken, AngularFireModule, FirebaseNameOrConfigToken } from '@angular/fire';
4-
import { AngularFireAnalytics, AngularFireAnalyticsModule, AUTOMATICALLY_SET_CURRENT_SCREEN, AUTOMATICALLY_LOG_SCREEN_VIEWS, ANALYTICS_COLLECTION_ENABLED, AUTOMATICALLY_TRACK_USER_IDENTIFIER, APP_VERSION, APP_NAME } from '@angular/fire/analytics';
4+
import { AngularFireAnalytics, AngularFireAnalyticsModule, ANALYTICS_COLLECTION_ENABLED, APP_VERSION, APP_NAME } from '@angular/fire/analytics';
55
import { COMMON_CONFIG } from './test-config';
66

77

@@ -32,7 +32,7 @@ describe('AngularFireAnalytics', () => {
3232
});
3333

3434
it('should have the Firebase Functions instance', () => {
35-
expect(analytics.analytics).toBeDefined();
35+
expect(analytics.app).toBeDefined();
3636
});
3737

3838
});
@@ -52,10 +52,7 @@ describe('AngularFireAnalytics with different app', () => {
5252
providers: [
5353
{ provide: FirebaseNameOrConfigToken, useValue: FIREBASE_APP_NAME_TOO },
5454
{ provide: FirebaseOptionsToken, useValue: COMMON_CONFIG },
55-
{ provide: AUTOMATICALLY_SET_CURRENT_SCREEN, useValue: true }
56-
{ provide: AUTOMATICALLY_LOG_SCREEN_VIEWS, useValue: true },
5755
{ provide: ANALYTICS_COLLECTION_ENABLED, useValue: true },
58-
{ provide: AUTOMATICALLY_TRACK_USER_IDENTIFIER, useValue: true },
5956
{ provide: APP_VERSION, useValue: '0.0' },
6057
{ provide: APP_NAME, useValue: 'Test App!' }
6158
]
@@ -78,7 +75,7 @@ describe('AngularFireAnalytics with different app', () => {
7875
});
7976

8077
it('should have the Firebase Functions instance', () => {
81-
expect(analytics.analytics).toBeDefined();
78+
expect(analytics.app).toBeDefined();
8279
});
8380

8481
});

src/analytics/analytics.ts

+16-15
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,25 @@ export class AngularFireAnalytics {
5555
zone: NgZone
5656
) {
5757

58-
if (!isPlatformBrowser(platformId)) {
59-
// TODO flush out non-browser support
58+
if (isPlatformBrowser(platformId)) {
59+
60+
window[DATA_LAYER_NAME] = window[DATA_LAYER_NAME] || [];
61+
this.gtag = window[GTAG_FUNCTION_NAME] || function() { window[DATA_LAYER_NAME].push(arguments) }
62+
this.analyticsInitialized = zone.runOutsideAngular(() =>
63+
new Promise(resolve => {
64+
window[GTAG_FUNCTION_NAME] = (...args: any[]) => {
65+
if (args[0] == 'js') { resolve() }
66+
this.gtag(...args);
67+
}
68+
})
69+
);
70+
71+
} else {
72+
6073
this.analyticsInitialized = Promise.resolve();
6174
this.gtag = () => {}
62-
// TODO fix the proxy for the server
63-
return this;
64-
}
6575

66-
window[DATA_LAYER_NAME] = window[DATA_LAYER_NAME] || [];
67-
this.gtag = window[GTAG_FUNCTION_NAME] || function() { window[DATA_LAYER_NAME].push(arguments) }
68-
this.analyticsInitialized = zone.runOutsideAngular(() =>
69-
new Promise(resolve => {
70-
window[GTAG_FUNCTION_NAME] = (...args: any[]) => {
71-
if (args[0] == 'js') { resolve() }
72-
this.gtag(...args);
73-
}
74-
})
75-
);
76+
}
7677

7778
if (providedAppName) { this.updateConfig({ [APP_NAME_KEY]: providedAppName }) }
7879
if (providedAppVersion) { this.updateConfig({ [APP_VERSION_KEY]: providedAppVersion }) }

src/core/angularfire2.ts

+16-17
Original file line numberDiff line numberDiff line change
@@ -84,25 +84,24 @@ export const ɵlazySDKProxy = (klass: any, observable: Observable<any>, zone: Ng
8484
get: (_, name) => zone.runOutsideAngular(() =>
8585
klass[name] || new Proxy(() =>
8686
observable.toPromise().then(mod => {
87-
if (mod) {
88-
const ret = mod[name];
89-
// TODO move to proper type guards
90-
if (typeof ret == 'function') {
91-
return ret.bind(mod);
92-
} else if (ret && ret.then) {
93-
return ret.then((res:any) => zone.run(() => res));
94-
} else {
95-
return zone.run(() => ret);
96-
}
87+
const ret = mod && mod[name];
88+
// TODO move to proper type guards
89+
if (typeof ret == 'function') {
90+
return ret.bind(mod);
91+
} else if (ret && ret.then) {
92+
return ret.then((res:any) => zone.run(() => res));
9793
} else {
98-
// the module is not available, SSR maybe?
99-
// TODO dig into this deeper, maybe return a never resolving promise?
100-
return () => {};
94+
return zone.run(() => ret);
10195
}
10296
}), {
103-
get: (self, name) => self()[name],
104-
// TODO handle callbacks
105-
apply: (self, _, args) => self().then(it => it(...args))
106-
})
97+
get: (self, name) => {
98+
const ret = self();
99+
// TODO proxy so we can have apply?
100+
return ret && ret[name] || (() => {});
101+
},
102+
// TODO handle callbacks as transparently as I can
103+
apply: (self, _, args) => self().then(it => it && it(...args))
104+
}
105+
)
107106
)
108107
});

src/remote-config/remote-config.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe('AngularFireRemoteConfig', () => {
3131
});
3232

3333
it('should have the Firebase Functions instance', () => {
34-
expect(rc.remoteConfig).toBeDefined();
34+
expect(rc.getValue).toBeDefined();
3535
});
3636

3737
});
@@ -73,7 +73,7 @@ describe('AngularFireRemoteConfig with different app', () => {
7373
});
7474

7575
it('should have the Firebase Functions instance', () => {
76-
expect(rc.remoteConfig).toBeDefined();
76+
expect(rc.getValue).toBeDefined();
7777
});
7878

7979
});

0 commit comments

Comments
 (0)