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' ;
2
2
import { Subscription , from , Observable , empty , of } from 'rxjs' ;
3
3
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' ;
5
5
import { runOutsideAngular } from '@angular/fire' ;
6
- import { AngularFireAnalytics } from './analytics' ;
6
+ import { AngularFireAnalytics , DEBUG_MODE } from './analytics' ;
7
7
import { User } from 'firebase/app' ;
8
8
import { Title } from '@angular/platform-browser' ;
9
9
import { isPlatformBrowser } from '@angular/common' ;
@@ -27,6 +27,8 @@ const DEFAULT_SCREEN_CLASS = '???';
27
27
const NG_PRIMARY_OUTLET = 'primary' ;
28
28
const SCREEN_INSTANCE_DELIMITER = '#' ;
29
29
30
+ const ANNOTATIONS = '__annotations__' ;
31
+
30
32
@Injectable ( )
31
33
export class ScreenTrackingService implements OnDestroy {
32
34
@@ -38,7 +40,9 @@ export class ScreenTrackingService implements OnDestroy {
38
40
@Optional ( ) title :Title ,
39
41
componentFactoryResolver : ComponentFactoryResolver ,
40
42
@Inject ( PLATFORM_ID ) platformId :Object ,
41
- zone : NgZone
43
+ @Optional ( ) @Inject ( DEBUG_MODE ) debugModeEnabled :boolean | null ,
44
+ zone : NgZone ,
45
+ injector : Injector
42
46
) {
43
47
if ( ! router || ! isPlatformBrowser ( platformId ) ) { return this }
44
48
zone . runOutsideAngular ( ( ) => {
@@ -70,23 +74,40 @@ export class ScreenTrackingService implements OnDestroy {
70
74
// it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style
71
75
return of ( { ...params , [ SCREEN_CLASS_KEY ] : loadChildren . split ( '#' ) [ 1 ] } ) ;
72
76
} else if ( typeof component === 'string' ) {
73
- // TODO figure out when this would this be a string
74
77
return of ( { ...params , [ SCREEN_CLASS_KEY ] : component } ) ;
75
78
} else if ( component ) {
76
79
const componentFactory = componentFactoryResolver . resolveComponentFactory ( component ) ;
77
80
return of ( { ...params , [ SCREEN_CLASS_KEY ] : componentFactory . selector } ) ;
78
81
} else if ( loadChildren ) {
79
82
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
+ ) ;
88
110
} else {
89
- // TODO figure out what forms of router events I might be missing
90
111
return of ( { ...params , [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ) ;
91
112
}
92
113
} ) ,
@@ -116,6 +137,7 @@ export class ScreenTrackingService implements OnDestroy {
116
137
[ FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY ] : prior [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] ,
117
138
...current !
118
139
} : current ! ) ,
140
+ tap ( params => debugModeEnabled && console . info ( SCREEN_VIEW_EVENT , params ) ) ,
119
141
tap ( params => zone . runOutsideAngular ( ( ) => analytics . logEvent ( SCREEN_VIEW_EVENT , params ) ) )
120
142
) . subscribe ( ) ;
121
143
} ) ;
0 commit comments