1- import  {  Injectable ,  Optional ,  NgZone ,  OnDestroy  }  from  '@angular/core' ; 
1+ import  {  Injectable ,  Optional ,  NgZone ,  OnDestroy ,   ComponentFactoryResolver ,   Inject ,   PLATFORM_ID  }  from  '@angular/core' ; 
22import  {  Subscription ,  from ,  Observable ,  empty ,  of  }  from  'rxjs' ; 
33import  {  filter ,  withLatestFrom ,  switchMap ,  map ,  tap ,  pairwise ,  startWith ,  groupBy ,  mergeMap  }  from  'rxjs/operators' ; 
44import  {  Router ,  NavigationEnd ,  ActivationEnd  }  from  '@angular/router' ; 
55import  {  runOutsideAngular  }  from  '@angular/fire' ; 
66import  {  AngularFireAnalytics  }  from  './analytics' ; 
77import  {  User  }  from  'firebase/app' ; 
88import  {  Title  }  from  '@angular/platform-browser' ; 
9+ import  {  isPlatformBrowser  }  from  '@angular/common' ; 
910
10- // Gold seems to take page_title and screen_path but the v2 protocol doesn't seem 
11- // to allow any class name, obviously v2 was designed for the basic web. I'm still 
12- // sending firebase_screen_class (largely for BQ compatability) but the Firebase Console 
13- // doesn't appear to be consuming the event properties. 
14- // FWIW I'm seeing notes that firebase_* is depreciated in favor of ga_* in GMS... so IDK  
15- const  SCREEN_NAME_KEY  =  'screen_name' ; 
16- const  PAGE_PATH_KEY  =  'page_path' ; 
17- const  EVENT_ORIGIN_KEY  =  'event_origin' ; 
18- const  FIREBASE_SCREEN_NAME_KEY  =  'firebase_screen' ; 
11+ const  FIREBASE_EVENT_ORIGIN_KEY  =  'firebase_event_origin' ; 
12+ const  FIREBASE_PREVIOUS_SCREEN_CLASS_KEY  =  'firebase_previous_class' ; 
13+ const  FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY   =  'firebase_previous_id' ; 
14+ const  FIREBASE_PREVIOUS_SCREEN_NAME_KEY  =  'firebase_previous_screen' ; 
1915const  FIREBASE_SCREEN_CLASS_KEY  =  'firebase_screen_class' ; 
16+ const  FIREBASE_SCREEN_INSTANCE_ID_KEY  =  'firebase_screen_id' ; 
17+ const  FIREBASE_SCREEN_NAME_KEY  =  'firebase_screen' ; 
2018const  OUTLET_KEY  =  'outlet' ; 
19+ const  PAGE_PATH_KEY  =  'page_path' ; 
2120const  PAGE_TITLE_KEY  =  'page_title' ; 
22- const  PREVIOUS_SCREEN_CLASS_KEY  =  'firebase_previous_class' ; 
23- const  PREVIOUS_SCREEN_INSTANCE_ID_KEY   =  'firebase_previous_id' ; 
24- const  PREVIOUS_SCREEN_NAME_KEY  =  'firebase_previous_screen' ; 
25- const  SCREEN_INSTANCE_ID_KEY  =  'firebase_screen_id' ; 
26- 
27- // Do I need these? 
2821const  SCREEN_CLASS_KEY  =  'screen_class' ; 
29- const  GA_SCREEN_CLASS_KEY  =  'ga_screen_class' ; 
30- const  GA_SCREEN_NAME_KEY  =  'ga_screen' ; 
22+ const  SCREEN_NAME_KEY  =  'screen_name' ; 
3123
3224const  SCREEN_VIEW_EVENT  =  'screen_view' ; 
3325const  EVENT_ORIGIN_AUTO  =  'auto' ; 
@@ -44,77 +36,89 @@ export class ScreenTrackingService implements OnDestroy {
4436      analytics : AngularFireAnalytics , 
4537      @Optional ( )  router :Router , 
4638      @Optional ( )  title :Title , 
39+       componentFactoryResolver : ComponentFactoryResolver , 
40+       @Inject ( PLATFORM_ID )  platformId :Object , 
4741      zone : NgZone 
4842    )  { 
49-         if  ( ! router )  {  return  this  } 
50-         const  activationEndEvents  =  router . events . pipe ( filter < ActivationEnd > ( e  =>  e  instanceof  ActivationEnd ) ) ; 
51-         const  navigationEndEvents  =  router . events . pipe ( filter < NavigationEnd > ( e  =>  e  instanceof  NavigationEnd ) ) ; 
52-         this . disposable  =  navigationEndEvents . pipe ( 
53-             withLatestFrom ( activationEndEvents ) , 
54-             switchMap ( ( [ navigationEnd ,  activationEnd ] )  =>  { 
55-                 // SEMVER: start using optional chains and nullish coalescing once we support newer typescript 
56-                 const  page_path  =  navigationEnd . url ; 
57-                 const  screen_name  =  activationEnd . snapshot . routeConfig  &&  activationEnd . snapshot . routeConfig . path  ||  page_path ; 
58-                 const  params  =  { 
59-                     [ SCREEN_NAME_KEY ] : screen_name , 
60-                     [ PAGE_PATH_KEY ] : page_path , 
61-                     [ EVENT_ORIGIN_KEY ] : EVENT_ORIGIN_AUTO , 
62-                     // TODO remove unneeded, just testing here 
63-                     [ FIREBASE_SCREEN_NAME_KEY ] : `${ screen_name }   (firebase)` , 
64-                     [ GA_SCREEN_NAME_KEY ] : `${ screen_name }   (ga)` , 
65-                     [ OUTLET_KEY ] : activationEnd . snapshot . outlet 
66-                 } ; 
67-                 if  ( title )  {  params [ PAGE_TITLE_KEY ]  =  title . getTitle ( )  } 
68-                 const  component  =  activationEnd . snapshot . component ; 
69-                 const  routeConfig  =  activationEnd . snapshot . routeConfig ; 
70-                 // TODO maybe not lean on _loadedConfig... 
71-                 const  loadedConfig  =  routeConfig  &&  ( routeConfig  as  any ) . _loadedConfig ; 
72-                 const  loadChildren  =  routeConfig  &&  routeConfig . loadChildren ; 
73-                 if  ( component )  { 
74-                     return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : nameOrToString ( component )  } ) ; 
75-                 }  else  if  ( loadedConfig  &&  loadedConfig . module  &&  loadedConfig . module . _moduleType )  { 
76-                     return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : nameOrToString ( loadedConfig . module . _moduleType ) } ) ; 
77-                 }  else  if  ( typeof  loadChildren  ===  "string" )  { 
78-                     // TODO is the an older lazy loading style? parse, if so 
79-                     return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : loadChildren  } ) ; 
80-                 }  else  if  ( loadChildren )  { 
81-                     // TODO look into the other return types here 
82-                     return  from ( loadChildren ( )  as  Promise < any > ) . pipe ( map ( child  =>  ( { ...params ,  [ SCREEN_CLASS_KEY ] : nameOrToString ( child )  } ) ) ) ; 
83-                 }  else  { 
84-                     // TODO figure out what forms of router events I might be missing 
85-                     return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ) ; 
86-                 } 
87-             } ) , 
88-             map ( params  =>  ( { 
89-                 // TODO remove unneeded, just testing here 
90-                 [ GA_SCREEN_CLASS_KEY ] : `${ params [ SCREEN_CLASS_KEY ] }   (ga)` , 
91-                 [ FIREBASE_SCREEN_CLASS_KEY ] : `${ params [ SCREEN_CLASS_KEY ] }   (firebase)` , 
92-                 [ SCREEN_INSTANCE_ID_KEY ] : getScreenInstanceID ( params ) , 
93-                 ...params 
94-             } ) ) , 
95-             tap ( params  =>  { 
96-                 // TODO perhaps I can be smarter about this, bubble events up to the nearest outlet? 
97-                 if  ( params [ OUTLET_KEY ]  ==  NG_PRIMARY_OUTLET )  { 
98-                     // TODO do we want to track the firebase_ attributes? 
99-                     analytics . setCurrentScreen ( params [ SCREEN_NAME_KEY ] ) ; 
100-                     analytics . updateConfig ( { 
101-                         [ PAGE_PATH_KEY ] : params [ PAGE_PATH_KEY ] , 
102-                         [ SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ] 
103-                     } ) ; 
104-                     if  ( title )  {  analytics . updateConfig ( {  [ PAGE_TITLE_KEY ] : params [ PAGE_TITLE_KEY ]  } )  } 
105-                 } 
106-             } ) , 
107-             groupBy ( params  =>  params [ OUTLET_KEY ] ) , 
108-             mergeMap ( group  =>  group . pipe ( startWith ( undefined ) ,  pairwise ( ) ) ) , 
109-             map ( ( [ prior ,  current ] )  =>  prior  ? { 
110-                 [ PREVIOUS_SCREEN_CLASS_KEY ] : prior [ SCREEN_CLASS_KEY ] , 
111-                 [ PREVIOUS_SCREEN_NAME_KEY ] : prior [ SCREEN_NAME_KEY ] , 
112-                 [ PREVIOUS_SCREEN_INSTANCE_ID_KEY ] : prior [ SCREEN_INSTANCE_ID_KEY ] , 
113-                 ...current ! 
114-             }  : current ! ) , 
115-             tap ( params  =>  analytics . logEvent ( SCREEN_VIEW_EVENT ,  params ) ) , 
116-             runOutsideAngular ( zone ) 
117-         ) . subscribe ( ) ; 
43+         if  ( ! router  ||  ! isPlatformBrowser ( platformId ) )  {  return  this  } 
44+         zone . runOutsideAngular ( ( )  =>  { 
45+             const  activationEndEvents  =  router . events . pipe ( filter < ActivationEnd > ( e  =>  e  instanceof  ActivationEnd ) ) ; 
46+             const  navigationEndEvents  =  router . events . pipe ( filter < NavigationEnd > ( e  =>  e  instanceof  NavigationEnd ) ) ; 
47+             this . disposable  =  navigationEndEvents . pipe ( 
48+                 withLatestFrom ( activationEndEvents ) , 
49+                 switchMap ( ( [ navigationEnd ,  activationEnd ] )  =>  { 
50+                     // SEMVER: start using optional chains and nullish coalescing once we support newer typescript 
51+                     const  page_path  =  navigationEnd . url ; 
52+                     const  screen_name  =  activationEnd . snapshot . routeConfig  &&  activationEnd . snapshot . routeConfig . path  ||  page_path ; 
53+                     const  params  =  { 
54+                         [ SCREEN_NAME_KEY ] : screen_name , 
55+                         [ PAGE_PATH_KEY ] : page_path , 
56+                         [ FIREBASE_EVENT_ORIGIN_KEY ] : EVENT_ORIGIN_AUTO , 
57+                         [ FIREBASE_SCREEN_NAME_KEY ] : screen_name , 
58+                         [ OUTLET_KEY ] : activationEnd . snapshot . outlet 
59+                     } ; 
60+                     if  ( title )  { 
61+                         params [ PAGE_TITLE_KEY ]  =  title . getTitle ( ) 
62+                     } 
63+                     const  component  =  activationEnd . snapshot . component ; 
64+                     const  routeConfig  =  activationEnd . snapshot . routeConfig ; 
65+                     const  loadChildren  =  routeConfig  &&  routeConfig . loadChildren ; 
66+                     // TODO figure out how to handle minification 
67+                     if  ( typeof  loadChildren  ===  "string" )  { 
68+                         // SEMVER: this is the older lazy load style "./path#ClassName", drop this when we drop old ng 
69+                         // TODO is it worth seeing if I can look up the component factory selector from the module name? 
70+                         // it's lazy so it's not registered with componentFactoryResolver yet... seems a pain for a depreciated style 
71+                         return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : loadChildren . split ( '#' ) [ 1 ] } ) ; 
72+                     }  else  if  ( typeof  component  ===  'string' )  { 
73+                         // TODO figure out when this would this be a string 
74+                         return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : component  } ) ; 
75+                     }  else  if  ( component )  { 
76+                         const  componentFactory  =  componentFactoryResolver . resolveComponentFactory ( component ) ; 
77+                         return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : componentFactory . selector  } ) ; 
78+                     }  else  if  ( loadChildren )  { 
79+                         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+                         } ) ) ; 
88+                     }  else  { 
89+                         // TODO figure out what forms of router events I might be missing 
90+                         return  of ( { ...params ,  [ SCREEN_CLASS_KEY ] : DEFAULT_SCREEN_CLASS } ) ; 
91+                     } 
92+                 } ) , 
93+                 map ( params  =>  ( { 
94+                     [ FIREBASE_SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ] , 
95+                     [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] : getScreenInstanceID ( params ) , 
96+                     ...params 
97+                 } ) ) , 
98+                 tap ( params  =>  { 
99+                     // TODO perhaps I can be smarter about this, bubble events up to the nearest outlet? 
100+                     if  ( params [ OUTLET_KEY ]  ==  NG_PRIMARY_OUTLET )  { 
101+                         analytics . setCurrentScreen ( params [ SCREEN_NAME_KEY ] ) ; 
102+                         analytics . updateConfig ( { 
103+                             [ PAGE_PATH_KEY ] : params [ PAGE_PATH_KEY ] , 
104+                             [ SCREEN_CLASS_KEY ] : params [ SCREEN_CLASS_KEY ] 
105+                         } ) ; 
106+                         if  ( title )  { 
107+                             analytics . updateConfig ( {  [ PAGE_TITLE_KEY ] : params [ PAGE_TITLE_KEY ]  } ) 
108+                         } 
109+                     } 
110+                 } ) , 
111+                 groupBy ( params  =>  params [ OUTLET_KEY ] ) , 
112+                 mergeMap ( group  =>  group . pipe ( startWith ( undefined ) ,  pairwise ( ) ) ) , 
113+                 map ( ( [ prior ,  current ] )  =>  prior  ? { 
114+                     [ FIREBASE_PREVIOUS_SCREEN_CLASS_KEY ] : prior [ SCREEN_CLASS_KEY ] , 
115+                     [ FIREBASE_PREVIOUS_SCREEN_NAME_KEY ] : prior [ SCREEN_NAME_KEY ] , 
116+                     [ FIREBASE_PREVIOUS_SCREEN_INSTANCE_ID_KEY ] : prior [ FIREBASE_SCREEN_INSTANCE_ID_KEY ] , 
117+                     ...current ! 
118+                 }  : current ! ) , 
119+                 tap ( params  =>  zone . runOutsideAngular ( ( )  =>  analytics . logEvent ( SCREEN_VIEW_EVENT ,  params ) ) ) 
120+             ) . subscribe ( ) ; 
121+         } ) ; 
118122    } 
119123
120124    ngOnDestroy ( )  { 
@@ -165,6 +169,4 @@ const getScreenInstanceID = (params:{[key:string]: any}) => {
165169        knownScreenInstanceIDs [ screenInstanceKey ]  =  ret ; 
166170        return  ret ; 
167171    } 
168- } 
169- 
170- const  nameOrToString  =  ( it :any ) : string  =>  it . name  ||  it . toString ( ) ; 
172+ } 
0 commit comments