Skip to content

Commit 69f94bf

Browse files
authored
fix: resolve routing component cleanup, preserve query params and replaceUrl (#69)
1 parent 8fa3c1d commit 69f94bf

File tree

5 files changed

+110
-19
lines changed

5 files changed

+110
-19
lines changed

packages/angular/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nativescript/angular",
3-
"version": "13.0.3",
3+
"version": "13.0.4-alpha.1",
44
"homepage": "https://nativescript.org/",
55
"repository": {
66
"type": "git",

packages/angular/src/lib/legacy/router/ns-location-strategy.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class NSLocationStrategy extends LocationStrategy {
7272
this.pushStateInternal(state, title, url, queryParams);
7373
}
7474

75-
pushStateInternal(state: any, title: string, url: string, queryParams: string): void {
75+
pushStateInternal(state: any, title: string, url: string, queryParams: string, replace = false): void {
7676
const urlSerializer = new DefaultUrlSerializer();
7777
this.currentUrlTree = urlSerializer.parse(url);
7878
const urlTreeRoot = this.currentUrlTree.root;
@@ -84,7 +84,7 @@ export class NSLocationStrategy extends LocationStrategy {
8484
const outletKey = this.getOutletKey(this.getSegmentGroupFullPath(segmentGroup), 'primary');
8585
const outlet = this.findOutlet(outletKey);
8686

87-
if (outlet && this.updateStates(outlet, segmentGroup, this.currentUrlTree.queryParams)) {
87+
if (outlet && this.updateStates(outlet, segmentGroup, this.currentUrlTree.queryParams, replace)) {
8888
this.currentOutlet = outlet; // If states updated
8989
} else if (!outlet) {
9090
// tslint:disable-next-line:max-line-length
@@ -121,11 +121,11 @@ export class NSLocationStrategy extends LocationStrategy {
121121
this.currentOutlet = outlet;
122122
} else if (this._modalNavigationDepth > 0 && outlet.showingModal && !containsLastState) {
123123
// Navigation inside modal view.
124-
this.upsertModalOutlet(outlet, currentSegmentGroup, this.currentUrlTree.queryParams);
124+
this.upsertModalOutlet(outlet, currentSegmentGroup, this.currentUrlTree.queryParams, replace);
125125
} else {
126126
outlet.parent = parentOutlet;
127127

128-
if (this.updateStates(outlet, currentSegmentGroup, this.currentUrlTree.queryParams)) {
128+
if (this.updateStates(outlet, currentSegmentGroup, this.currentUrlTree.queryParams, replace)) {
129129
this.currentOutlet = outlet; // If states updated
130130
}
131131
}
@@ -144,6 +144,7 @@ export class NSLocationStrategy extends LocationStrategy {
144144
if (NativeScriptDebug.isLogEnabled()) {
145145
NativeScriptDebug.routerLog('NSLocationStrategy.replaceState changing existing state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
146146
}
147+
this.pushStateInternal(state, title, url, queryParams, true);
147148
} else {
148149
if (NativeScriptDebug.isLogEnabled()) {
149150
NativeScriptDebug.routerLog('NSLocationStrategy.replaceState pushing new state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
@@ -381,6 +382,7 @@ export class NSLocationStrategy extends LocationStrategy {
381382
clearHistory: isPresent(options.clearHistory) ? options.clearHistory : false,
382383
animated: isPresent(options.animated) ? options.animated : true,
383384
transition: options.transition,
385+
replaceUrl: options.replaceUrl,
384386
};
385387

386388
if (NativeScriptDebug.isLogEnabled()) {
@@ -532,7 +534,7 @@ export class NSLocationStrategy extends LocationStrategy {
532534
return outlet;
533535
}
534536

535-
private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup, queryParams: Params): boolean {
537+
private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup, queryParams: Params, replace = false): boolean {
536538
const isNewPage = outlet.states.length === 0;
537539
const lastState = outlet.states[outlet.states.length - 1];
538540
const equalStateUrls = outlet.containsTopState(currentSegmentGroup.toString());
@@ -545,6 +547,9 @@ export class NSLocationStrategy extends LocationStrategy {
545547
};
546548

547549
if (!lastState || !equalStateUrls) {
550+
if (replace) {
551+
outlet.states.pop();
552+
}
548553
outlet.states.push(locationState);
549554

550555
// Update last state segmentGroup of parent Outlet.
@@ -553,6 +558,11 @@ export class NSLocationStrategy extends LocationStrategy {
553558
}
554559

555560
return true;
561+
} else {
562+
if (lastState && equalStateUrls) {
563+
// update query params for last state
564+
lastState.queryParams = { ...queryParams };
565+
}
556566
}
557567

558568
return false;
@@ -649,7 +659,7 @@ export class NSLocationStrategy extends LocationStrategy {
649659
}
650660
}
651661

652-
private upsertModalOutlet(parentOutlet: Outlet, segmentedGroup: UrlSegmentGroup, queryParams: Params) {
662+
private upsertModalOutlet(parentOutlet: Outlet, segmentedGroup: UrlSegmentGroup, queryParams: Params, replace = false) {
653663
let currentModalOutlet = this.findOutletByModal(this._modalNavigationDepth);
654664

655665
// We want to treat every p-r-o as a standalone Outlet.
@@ -666,7 +676,7 @@ export class NSLocationStrategy extends LocationStrategy {
666676
// tslint:disable-next-line:max-line-length
667677
currentModalOutlet = this.createOutlet(outletKey, outletPath, segmentedGroup, parentOutlet, this._modalNavigationDepth, queryParams);
668678
this.currentOutlet = currentModalOutlet;
669-
} else if (this.updateStates(currentModalOutlet, segmentedGroup, queryParams)) {
679+
} else if (this.updateStates(currentModalOutlet, segmentedGroup, queryParams, replace)) {
670680
this.currentOutlet = currentModalOutlet; // If states updated
671681
}
672682
}

packages/angular/src/lib/legacy/router/ns-location-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export interface NavigationOptions {
1313
clearHistory?: boolean;
1414
animated?: boolean;
1515
transition?: NavigationTransition;
16+
replaceUrl?: boolean;
1617
}
1718

1819
export class Outlet {

packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -120,12 +120,38 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
120120

121121
return shouldDetach;
122122
}
123+
protected findValidOutletAndKey(targetRoute: ActivatedRouteSnapshot) {
124+
let route = targetRoute;
125+
const routeOutletKey = this.location.getRouteFullPath(route);
126+
let outletKey = routeOutletKey;
127+
let outlet = this.location.findOutlet(outletKey, route);
128+
while (!outlet) {
129+
if (!route.parent) {
130+
return { outlet: null, outletKey: routeOutletKey };
131+
}
132+
route = route.parent;
133+
outletKey = this.location.getRouteFullPath(route);
134+
outlet = this.location.findOutlet(outletKey, route);
135+
}
136+
137+
if (outlet) {
138+
while (!outlet.outletKeys.includes(outletKey)) {
139+
if (!route.parent) {
140+
NativeScriptDebug.routeReuseStrategyLog(`Could not find valid outlet key for route: ${targetRoute}.`);
141+
return { outlet, outletKey: routeOutletKey };
142+
}
143+
route = route.parent;
144+
outletKey = this.location.getRouteFullPath(route);
145+
}
146+
}
147+
148+
return { outlet, outletKey };
149+
}
123150

124151
shouldAttach(route: ActivatedRouteSnapshot): boolean {
125152
route = findTopActivatedRouteNodeForOutlet(route);
126153

127-
const outletKey = this.location.getRouteFullPath(route);
128-
const outlet = this.location.findOutlet(outletKey, route);
154+
const { outlet, outletKey } = this.findValidOutletAndKey(route);
129155
const cache = this.cacheByOutlet[outletKey];
130156
if (!cache) {
131157
return false;
@@ -154,7 +180,7 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
154180
NativeScriptDebug.routeReuseStrategyLog(`store key: ${key}, state: ${state}`);
155181
}
156182

157-
const outletKey = this.location.getRouteFullPath(route);
183+
const { outletKey } = this.findValidOutletAndKey(route);
158184

159185
// tslint:disable-next-line:max-line-length
160186
const cache = (this.cacheByOutlet[outletKey] = this.cacheByOutlet[outletKey] || new DetachedStateCache());
@@ -183,8 +209,7 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
183209
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
184210
route = findTopActivatedRouteNodeForOutlet(route);
185211

186-
const outletKey = this.location.getRouteFullPath(route);
187-
const outlet = this.location.findOutlet(outletKey, route);
212+
const { outlet, outletKey } = this.findValidOutletAndKey(route);
188213
const cache = this.cacheByOutlet[outletKey];
189214
if (!cache) {
190215
return null;
@@ -230,6 +255,19 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
230255
}
231256
}
232257

258+
popCache(outletKey: string) {
259+
const cache = this.cacheByOutlet[outletKey];
260+
261+
if (cache) {
262+
if (cache.peek()) {
263+
const state: any = cache.pop()?.state;
264+
if (state?.componentRef) {
265+
destroyComponentRef(state?.componentRef);
266+
}
267+
}
268+
}
269+
}
270+
233271
clearModalCache(outletKey: string) {
234272
const cache = this.cacheByOutlet[outletKey];
235273

packages/angular/src/lib/legacy/router/page-router-outlet.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Attribute, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, Inject, InjectionToken, Injector, OnDestroy, EventEmitter, Output, Type, ViewContainerRef, ElementRef, InjectFlags, NgZone } from '@angular/core';
22
import { ActivatedRoute, ActivatedRouteSnapshot, ChildrenOutletContexts, PRIMARY_OUTLET } from '@angular/router';
33

4-
import { Frame, Page, NavigatedData, profile } from '@nativescript/core';
4+
import { Frame, Page, NavigatedData, profile, NavigationEntry } from '@nativescript/core';
55

66
import { BehaviorSubject } from 'rxjs';
77

@@ -241,7 +241,25 @@ export class PageRouterOutlet implements OnDestroy {
241241
this._activatedRoute = activatedRoute;
242242
this.markActivatedRoute(activatedRoute);
243243

244-
this.locationStrategy._finishBackPageNavigation(this.frame);
244+
// we have a child with the same name, so we don't finish the back nav
245+
if (this.isFinalPageRouterOutlet()) {
246+
this.locationStrategy._finishBackPageNavigation(this.frame);
247+
}
248+
}
249+
250+
private isFinalPageRouterOutlet() {
251+
let children = this.parentContexts.getContext(this.name)?.children;
252+
while (children) {
253+
const childContext = children.getContext(this.name);
254+
if (!childContext || !childContext.outlet) {
255+
return true;
256+
}
257+
if (childContext.outlet instanceof PageRouterOutlet) {
258+
return false;
259+
}
260+
children = childContext.children;
261+
}
262+
return true;
245263
}
246264

247265
/**
@@ -265,7 +283,9 @@ export class PageRouterOutlet implements OnDestroy {
265283
if (NativeScriptDebug.isLogEnabled()) {
266284
NativeScriptDebug.routerLog('Currently in page back navigation - component should be reattached instead of activated.');
267285
}
268-
this.locationStrategy._finishBackPageNavigation(this.frame);
286+
if (this.isFinalPageRouterOutlet()) {
287+
this.locationStrategy._finishBackPageNavigation(this.frame);
288+
}
269289
}
270290

271291
if (NativeScriptDebug.isLogEnabled()) {
@@ -364,28 +384,50 @@ export class PageRouterOutlet implements OnDestroy {
364384
});
365385

366386
const navOptions = this.locationStrategy._beginPageNavigation(this.frame);
387+
const isReplace = navOptions.replaceUrl && !navOptions.clearHistory;
367388

368389
// Clear refCache if navigation with clearHistory
369390
if (navOptions.clearHistory) {
370391
const clearCallback = () =>
371392
setTimeout(() => {
372393
if (this.outlet) {
373-
this.routeReuseStrategy.clearCache(this.outlet.outletKeys[0]);
394+
// potential alternative fix (only fix children of the current outlet)
395+
// const nests = outletKey.split('/');
396+
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.clearCache(key));
397+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearCache(key));
398+
}
399+
});
400+
401+
page.once(Page.navigatedToEvent, clearCallback);
402+
} else if (navOptions.replaceUrl) {
403+
const clearCallback = () =>
404+
setTimeout(() => {
405+
if (this.outlet) {
406+
// potential alternative fix (only fix children of the current outlet)
407+
// const nests = outletKey.split('/');
408+
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.popCache(key));
409+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.popCache(key));
374410
}
375411
});
376412

377413
page.once(Page.navigatedToEvent, clearCallback);
378414
}
379415

380-
this.frame.navigate({
416+
const navigationEntry: NavigationEntry = {
381417
create() {
382418
return page;
383419
},
384420
context: navigationContext,
385421
clearHistory: navOptions.clearHistory,
386422
animated: navOptions.animated,
387423
transition: navOptions.transition,
388-
});
424+
};
425+
426+
if (isReplace && this.frame.currentPage) {
427+
this.frame.replacePage(navigationEntry);
428+
} else {
429+
this.frame.navigate(navigationEntry);
430+
}
389431
}
390432

391433
// Find and mark the top activated route as an activated one.

0 commit comments

Comments
 (0)