Skip to content

Commit d9a7c63

Browse files
daem0ndevmhartington
authored andcommitted
fix(angular): support relative router links
Closes #17888, closes #16534, closes #16736, closes #16954
1 parent f2c8db9 commit d9a7c63

File tree

1 file changed

+75
-5
lines changed

1 file changed

+75
-5
lines changed

angular/src/directives/navigation/ion-router-outlet.ts

+75-5
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { Attribute, ChangeDetectorRef, ComponentFactoryResolver, ComponentRef, Directive, ElementRef, EventEmitter, Injector, NgZone, OnDestroy, OnInit, Optional, Output, SkipSelf, ViewContainerRef } from '@angular/core';
22
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';
45
import { Config } from '../../providers/config';
56
import { NavController } from '../../providers/nav-controller';
6-
77
import { StackController } from './stack-controller';
8-
import { RouteView, getUrl } from './stack-utils';
8+
import { getUrl, RouteView } from './stack-utils';
99

1010
@Directive({
1111
selector: 'ion-router-outlet',
1212
exportAs: 'outlet',
1313
inputs: ['animated', 'swipeGesture']
1414
})
1515
export class IonRouterOutlet implements OnDestroy, OnInit {
16-
1716
private activated: ComponentRef<any> | null = null;
1817
private activatedView: RouteView | null = null;
1918

@@ -23,6 +22,12 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
2322
private stackCtrl: StackController;
2423
private nativeEl: HTMLIonRouterOutletElement;
2524

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+
2631
tabsPrefix: string | undefined;
2732

2833
@Output() stackEvents = new EventEmitter<any>();
@@ -159,20 +164,31 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
159164
const context = this.getContext()!;
160165
context.children['contexts'] = saved;
161166
}
167+
// Updated activated route proxy for this component
168+
this.updateActivatedRouteProxy(cmpRef.instance, activatedRoute);
162169
} else {
163170
const snapshot = (activatedRoute as any)._futureSnapshot;
164171
const component = snapshot.routeConfig!.component as any;
165172
resolver = resolver || this.resolver;
166173

167174
const factory = resolver.resolveComponentFactory(component);
168175
const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
176+
const activatedRouteProxy = this.createActivatedRouteProxy(activatedRoute);
169177

170-
const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
178+
const injector = new OutletInjector(activatedRouteProxy, childContexts, this.location.injector);
171179
cmpRef = this.activated = this.location.createComponent(factory, this.location.length, injector);
172180

173181
// Calling `markForCheck` to make sure we will run the change detection when the
174182
// `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
175183
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+
176192
this.changeDetector.markForCheck();
177193
}
178194

@@ -212,6 +228,60 @@ export class IonRouterOutlet implements OnDestroy, OnInit {
212228
getActiveStackId(): string | undefined {
213229
return this.stackCtrl.getActiveStackId();
214230
}
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+
}
215285
}
216286

217287
class OutletInjector implements Injector {

0 commit comments

Comments
 (0)