1
1
import { Attribute , ChangeDetectorRef , ComponentFactoryResolver , ComponentRef , Directive , ElementRef , EventEmitter , Injector , NgZone , OnDestroy , OnInit , Optional , Output , SkipSelf , ViewContainerRef } from '@angular/core' ;
2
2
import { ActivatedRoute , ChildrenOutletContexts , OutletContext , PRIMARY_OUTLET , Router } from '@angular/router' ;
3
-
3
+ import { BehaviorSubject , Observable } from 'rxjs' ;
4
+ import { distinctUntilChanged , filter , switchMap } from 'rxjs/operators' ;
4
5
import { Config } from '../../providers/config' ;
5
6
import { NavController } from '../../providers/nav-controller' ;
6
-
7
7
import { StackController } from './stack-controller' ;
8
- import { RouteView , getUrl } from './stack-utils' ;
8
+ import { getUrl , RouteView } from './stack-utils' ;
9
9
10
10
@Directive ( {
11
11
selector : 'ion-router-outlet' ,
12
12
exportAs : 'outlet' ,
13
13
inputs : [ 'animated' , 'swipeGesture' ]
14
14
} )
15
15
export class IonRouterOutlet implements OnDestroy , OnInit {
16
-
17
16
private activated : ComponentRef < any > | null = null ;
18
17
private activatedView : RouteView | null = null ;
19
18
@@ -23,6 +22,12 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
23
22
private stackCtrl : StackController ;
24
23
private nativeEl : HTMLIonRouterOutletElement ;
25
24
25
+ // Maintain map of activated route proxies for each component instance
26
+ private proxyMap = new WeakMap < any , ActivatedRoute > ( ) ;
27
+
28
+ // Keep the latest activated route in a subject for the proxy routes to switch map to
29
+ private currentActivatedRoute$ = new BehaviorSubject < { component : any ; activatedRoute : ActivatedRoute } | null > ( null ) ;
30
+
26
31
tabsPrefix : string | undefined ;
27
32
28
33
@Output ( ) stackEvents = new EventEmitter < any > ( ) ;
@@ -159,20 +164,31 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
159
164
const context = this . getContext ( ) ! ;
160
165
context . children [ 'contexts' ] = saved ;
161
166
}
167
+ // Updated activated route proxy for this component
168
+ this . updateActivatedRouteProxy ( cmpRef . instance , activatedRoute ) ;
162
169
} else {
163
170
const snapshot = ( activatedRoute as any ) . _futureSnapshot ;
164
171
const component = snapshot . routeConfig ! . component as any ;
165
172
resolver = resolver || this . resolver ;
166
173
167
174
const factory = resolver . resolveComponentFactory ( component ) ;
168
175
const childContexts = this . parentContexts . getOrCreateContext ( this . name ) . children ;
176
+ const activatedRouteProxy = this . createActivatedRouteProxy ( activatedRoute ) ;
169
177
170
- const injector = new OutletInjector ( activatedRoute , childContexts , this . location . injector ) ;
178
+ const injector = new OutletInjector ( activatedRouteProxy , childContexts , this . location . injector ) ;
171
179
cmpRef = this . activated = this . location . createComponent ( factory , this . location . length , injector ) ;
172
180
173
181
// Calling `markForCheck` to make sure we will run the change detection when the
174
182
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
175
183
enteringView = this . stackCtrl . createView ( this . activated , activatedRoute ) ;
184
+
185
+ // Once the component is created, use the component instance to setup observables
186
+ this . setupProxyObservables ( activatedRouteProxy , cmpRef . instance ) ;
187
+
188
+ // Store references to the proxy by component
189
+ this . proxyMap . set ( cmpRef . instance , activatedRouteProxy ) ;
190
+ this . currentActivatedRoute$ . next ( { component : cmpRef . instance , activatedRoute } ) ;
191
+
176
192
this . changeDetector . markForCheck ( ) ;
177
193
}
178
194
@@ -212,6 +228,60 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
212
228
getActiveStackId ( ) : string | undefined {
213
229
return this . stackCtrl . getActiveStackId ( ) ;
214
230
}
231
+
232
+ /**
233
+ * Creates a proxy object that we can use to update activated route properties without losing reference
234
+ * in the component injector
235
+ */
236
+ private createActivatedRouteProxy ( activatedRoute : ActivatedRoute ) : ActivatedRoute {
237
+ const proxy : any = new ActivatedRoute ( ) ;
238
+ proxy . _futureSnapshot = ( activatedRoute as any ) . _futureSnapshot ;
239
+ proxy . _routerState = ( activatedRoute as any ) . _routerState ;
240
+ proxy . snapshot = activatedRoute . snapshot ;
241
+ proxy . outlet = activatedRoute . outlet ;
242
+ proxy . component = activatedRoute . component ;
243
+
244
+ return proxy as ActivatedRoute ;
245
+ }
246
+
247
+ private setupProxyObservables ( proxy : ActivatedRoute , component : any ) : void {
248
+ ( proxy as any ) . _paramMap = this . proxyObservable ( component , 'paramMap' ) ;
249
+ ( proxy as any ) . _queryParamMap = this . proxyObservable ( component , 'queryParamMap' ) ;
250
+ proxy . url = this . proxyObservable ( component , 'url' ) ;
251
+ proxy . params = this . proxyObservable ( component , 'params' ) ;
252
+ proxy . queryParams = this . proxyObservable ( component , 'queryParams' ) ;
253
+ proxy . fragment = this . proxyObservable ( component , 'fragment' ) ;
254
+ proxy . data = this . proxyObservable ( component , 'data' ) ;
255
+ }
256
+
257
+ /**
258
+ * Create a wrapped observable that will switch to the latest activated route matched by the given view id
259
+ */
260
+ private proxyObservable ( component : any , path : string ) : Observable < any > {
261
+ return this . currentActivatedRoute$ . pipe (
262
+ filter ( current => current !== null && current . component === component ) ,
263
+ switchMap ( current => current && ( current . activatedRoute as any ) [ path ] ) ,
264
+ distinctUntilChanged ( )
265
+ ) ;
266
+ }
267
+
268
+ /**
269
+ * Updates the given proxy route with data from the new incoming route
270
+ */
271
+ private updateActivatedRouteProxy ( component : any , activatedRoute : ActivatedRoute ) : void {
272
+ const proxy = this . proxyMap . get ( component ) ;
273
+ if ( ! proxy ) {
274
+ throw new Error ( `Could not find activated route proxy for view` ) ;
275
+ }
276
+
277
+ ( proxy as any ) . _futureSnapshot = ( activatedRoute as any ) . _futureSnapshot ;
278
+ ( proxy as any ) . _routerState = ( activatedRoute as any ) . _routerState ;
279
+ proxy . snapshot = activatedRoute . snapshot ;
280
+ proxy . outlet = activatedRoute . outlet ;
281
+ proxy . component = activatedRoute . component ;
282
+
283
+ this . currentActivatedRoute$ . next ( { component, activatedRoute } ) ;
284
+ }
215
285
}
216
286
217
287
class OutletInjector implements Injector {
0 commit comments