From 24f344519fe7be6444effcff9467a20534d4f79e Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 30 Mar 2022 16:53:38 -0300 Subject: [PATCH 1/7] fix: clear all history keys for outlet --- packages/angular/src/lib/legacy/router/page-router-outlet.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/angular/src/lib/legacy/router/page-router-outlet.ts b/packages/angular/src/lib/legacy/router/page-router-outlet.ts index 29e9585..93fb85f 100644 --- a/packages/angular/src/lib/legacy/router/page-router-outlet.ts +++ b/packages/angular/src/lib/legacy/router/page-router-outlet.ts @@ -370,7 +370,10 @@ export class PageRouterOutlet implements OnDestroy { const clearCallback = () => setTimeout(() => { if (this.outlet) { - this.routeReuseStrategy.clearCache(this.outlet.outletKeys[0]); + // potential alternative fix (only fix children of the current outlet) + // const nests = outletKey.split('/'); + // this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.clearCache(key)); + this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearCache(key)); } }); From 4a3f65b398b148d7e786e25f58270ed93fc09bd0 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 30 Mar 2022 17:30:23 -0300 Subject: [PATCH 2/7] fix: ensure we're using the correct key for storing routes --- .../src/lib/legacy/router/ns-location-utils.ts | 9 +++++++++ .../src/lib/legacy/router/ns-route-reuse-strategy.ts | 12 ++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/angular/src/lib/legacy/router/ns-location-utils.ts b/packages/angular/src/lib/legacy/router/ns-location-utils.ts index 200f8dd..9e0dee8 100644 --- a/packages/angular/src/lib/legacy/router/ns-location-utils.ts +++ b/packages/angular/src/lib/legacy/router/ns-location-utils.ts @@ -70,6 +70,15 @@ export class Outlet { }); } + getValidOutletKeyFromChildKey(key: string) { + if (this.outletKeys.includes(key)) { + return key; + } + const nests = key.split('/'); + const finalOutlets = this.outletKeys.sort((a, b) => b.split('/').length - a.split('/').length).filter((k) => k.split('/').length >= nests.length); + return finalOutlets.length > 0 ? finalOutlets[0] : null; + } + containsFrame(frame: Frame): boolean { return this.frames.indexOf(frame) > -1; } diff --git a/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts b/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts index cc645e8..0724632 100644 --- a/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts +++ b/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts @@ -126,7 +126,8 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { const outletKey = this.location.getRouteFullPath(route); const outlet = this.location.findOutlet(outletKey, route); - const cache = this.cacheByOutlet[outletKey]; + const storeKey = outlet?.getValidOutletKeyFromChildKey(outletKey) || outletKey; + const cache = this.cacheByOutlet[storeKey]; if (!cache) { return false; } @@ -155,9 +156,11 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { } const outletKey = this.location.getRouteFullPath(route); + const outlet = this.location.findOutlet(outletKey, route); + const storeKey = outlet?.getValidOutletKeyFromChildKey(outletKey) || outletKey; // tslint:disable-next-line:max-line-length - const cache = (this.cacheByOutlet[outletKey] = this.cacheByOutlet[outletKey] || new DetachedStateCache()); + const cache = (this.cacheByOutlet[storeKey] = this.cacheByOutlet[storeKey] || new DetachedStateCache()); if (state) { let isModal = false; @@ -172,7 +175,7 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { cache.pop(); if (!cache.length) { - delete this.cacheByOutlet[outletKey]; + delete this.cacheByOutlet[storeKey]; } } else { throw new Error("Trying to pop from DetachedStateCache but keys don't match. " + `expected: ${topItem.key} actual: ${key}`); @@ -185,7 +188,8 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { const outletKey = this.location.getRouteFullPath(route); const outlet = this.location.findOutlet(outletKey, route); - const cache = this.cacheByOutlet[outletKey]; + const storeKey = outlet?.getValidOutletKeyFromChildKey(outletKey) || outletKey; + const cache = this.cacheByOutlet[storeKey]; if (!cache) { return null; } From 5a306a63774036c7578f79ae430fb7da7f221487 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Wed, 30 Mar 2022 18:07:49 -0300 Subject: [PATCH 3/7] fix: unify outlet and outlkey fetching --- .../legacy/router/ns-route-reuse-strategy.ts | 47 ++++++++++++++----- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts b/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts index 0724632..4017d0d 100644 --- a/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts +++ b/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts @@ -120,14 +120,39 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { return shouldDetach; } + protected findValidOutletAndKey(targetRoute: ActivatedRouteSnapshot) { + let route = targetRoute; + const routeOutletKey = this.location.getRouteFullPath(route); + let outletKey = routeOutletKey; + let outlet = this.location.findOutlet(outletKey, route); + while (!outlet) { + if (!route.parent) { + return { outlet: null, outletKey: routeOutletKey }; + } + route = route.parent; + outletKey = this.location.getRouteFullPath(route); + outlet = this.location.findOutlet(outletKey, route); + } + + if (outlet) { + while (!outlet.outletKeys.includes(outletKey)) { + if (!route.parent) { + NativeScriptDebug.routeReuseStrategyLog(`Could not find valid outlet key for route: ${targetRoute}.`); + return { outlet, outletKey: routeOutletKey }; + } + route = route.parent; + outletKey = this.location.getRouteFullPath(route); + } + } + + return { outlet, outletKey }; + } shouldAttach(route: ActivatedRouteSnapshot): boolean { route = findTopActivatedRouteNodeForOutlet(route); - const outletKey = this.location.getRouteFullPath(route); - const outlet = this.location.findOutlet(outletKey, route); - const storeKey = outlet?.getValidOutletKeyFromChildKey(outletKey) || outletKey; - const cache = this.cacheByOutlet[storeKey]; + const { outlet, outletKey } = this.findValidOutletAndKey(route); + const cache = this.cacheByOutlet[outletKey]; if (!cache) { return false; } @@ -155,12 +180,10 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { NativeScriptDebug.routeReuseStrategyLog(`store key: ${key}, state: ${state}`); } - const outletKey = this.location.getRouteFullPath(route); - const outlet = this.location.findOutlet(outletKey, route); - const storeKey = outlet?.getValidOutletKeyFromChildKey(outletKey) || outletKey; + const { outletKey } = this.findValidOutletAndKey(route); // tslint:disable-next-line:max-line-length - const cache = (this.cacheByOutlet[storeKey] = this.cacheByOutlet[storeKey] || new DetachedStateCache()); + const cache = (this.cacheByOutlet[outletKey] = this.cacheByOutlet[outletKey] || new DetachedStateCache()); if (state) { let isModal = false; @@ -175,7 +198,7 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { cache.pop(); if (!cache.length) { - delete this.cacheByOutlet[storeKey]; + delete this.cacheByOutlet[outletKey]; } } else { throw new Error("Trying to pop from DetachedStateCache but keys don't match. " + `expected: ${topItem.key} actual: ${key}`); @@ -186,10 +209,8 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null { route = findTopActivatedRouteNodeForOutlet(route); - const outletKey = this.location.getRouteFullPath(route); - const outlet = this.location.findOutlet(outletKey, route); - const storeKey = outlet?.getValidOutletKeyFromChildKey(outletKey) || outletKey; - const cache = this.cacheByOutlet[storeKey]; + const { outlet, outletKey } = this.findValidOutletAndKey(route); + const cache = this.cacheByOutlet[outletKey]; if (!cache) { return null; } From 65886cf2e2de0fa48a5372ff23a1c4044c5cab88 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 31 Mar 2022 13:58:05 -0300 Subject: [PATCH 4/7] chore: revert unused function --- .../angular/src/lib/legacy/router/ns-location-utils.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/angular/src/lib/legacy/router/ns-location-utils.ts b/packages/angular/src/lib/legacy/router/ns-location-utils.ts index 9e0dee8..200f8dd 100644 --- a/packages/angular/src/lib/legacy/router/ns-location-utils.ts +++ b/packages/angular/src/lib/legacy/router/ns-location-utils.ts @@ -70,15 +70,6 @@ export class Outlet { }); } - getValidOutletKeyFromChildKey(key: string) { - if (this.outletKeys.includes(key)) { - return key; - } - const nests = key.split('/'); - const finalOutlets = this.outletKeys.sort((a, b) => b.split('/').length - a.split('/').length).filter((k) => k.split('/').length >= nests.length); - return finalOutlets.length > 0 ? finalOutlets[0] : null; - } - containsFrame(frame: Frame): boolean { return this.frames.indexOf(frame) > -1; } From dfdfd607f4b3ab2f0822580946c39eb0d5a7cfa2 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Mon, 4 Apr 2022 16:08:39 -0300 Subject: [PATCH 5/7] wip: experiments --- .../lib/legacy/router/ns-location-strategy.ts | 19 ++++--- .../lib/legacy/router/ns-location-utils.ts | 1 + .../legacy/router/ns-route-reuse-strategy.ts | 13 +++++ .../lib/legacy/router/page-router-outlet.ts | 49 +++++++++++++++++-- 4 files changed, 70 insertions(+), 12 deletions(-) diff --git a/packages/angular/src/lib/legacy/router/ns-location-strategy.ts b/packages/angular/src/lib/legacy/router/ns-location-strategy.ts index 6e8bd00..d6aa7b7 100644 --- a/packages/angular/src/lib/legacy/router/ns-location-strategy.ts +++ b/packages/angular/src/lib/legacy/router/ns-location-strategy.ts @@ -72,7 +72,7 @@ export class NSLocationStrategy extends LocationStrategy { this.pushStateInternal(state, title, url, queryParams); } - pushStateInternal(state: any, title: string, url: string, queryParams: string): void { + pushStateInternal(state: any, title: string, url: string, queryParams: string, replace = false): void { const urlSerializer = new DefaultUrlSerializer(); this.currentUrlTree = urlSerializer.parse(url); const urlTreeRoot = this.currentUrlTree.root; @@ -84,7 +84,7 @@ export class NSLocationStrategy extends LocationStrategy { const outletKey = this.getOutletKey(this.getSegmentGroupFullPath(segmentGroup), 'primary'); const outlet = this.findOutlet(outletKey); - if (outlet && this.updateStates(outlet, segmentGroup, this.currentUrlTree.queryParams)) { + if (outlet && this.updateStates(outlet, segmentGroup, this.currentUrlTree.queryParams, replace)) { this.currentOutlet = outlet; // If states updated } else if (!outlet) { // tslint:disable-next-line:max-line-length @@ -121,11 +121,11 @@ export class NSLocationStrategy extends LocationStrategy { this.currentOutlet = outlet; } else if (this._modalNavigationDepth > 0 && outlet.showingModal && !containsLastState) { // Navigation inside modal view. - this.upsertModalOutlet(outlet, currentSegmentGroup, this.currentUrlTree.queryParams); + this.upsertModalOutlet(outlet, currentSegmentGroup, this.currentUrlTree.queryParams, replace); } else { outlet.parent = parentOutlet; - if (this.updateStates(outlet, currentSegmentGroup, this.currentUrlTree.queryParams)) { + if (this.updateStates(outlet, currentSegmentGroup, this.currentUrlTree.queryParams, replace)) { this.currentOutlet = outlet; // If states updated } } @@ -144,6 +144,7 @@ export class NSLocationStrategy extends LocationStrategy { if (NativeScriptDebug.isLogEnabled()) { NativeScriptDebug.routerLog('NSLocationStrategy.replaceState changing existing state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`); } + this.pushStateInternal(state, title, url, queryParams, true); } else { if (NativeScriptDebug.isLogEnabled()) { NativeScriptDebug.routerLog('NSLocationStrategy.replaceState pushing new state: ' + `${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`); @@ -381,6 +382,7 @@ export class NSLocationStrategy extends LocationStrategy { clearHistory: isPresent(options.clearHistory) ? options.clearHistory : false, animated: isPresent(options.animated) ? options.animated : true, transition: options.transition, + replaceUrl: options.replaceUrl, }; if (NativeScriptDebug.isLogEnabled()) { @@ -532,7 +534,7 @@ export class NSLocationStrategy extends LocationStrategy { return outlet; } - private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup, queryParams: Params): boolean { + private updateStates(outlet: Outlet, currentSegmentGroup: UrlSegmentGroup, queryParams: Params, replace = false): boolean { const isNewPage = outlet.states.length === 0; const lastState = outlet.states[outlet.states.length - 1]; const equalStateUrls = outlet.containsTopState(currentSegmentGroup.toString()); @@ -545,6 +547,9 @@ export class NSLocationStrategy extends LocationStrategy { }; if (!lastState || !equalStateUrls) { + if (replace) { + outlet.states.pop(); + } outlet.states.push(locationState); // Update last state segmentGroup of parent Outlet. @@ -649,7 +654,7 @@ export class NSLocationStrategy extends LocationStrategy { } } - private upsertModalOutlet(parentOutlet: Outlet, segmentedGroup: UrlSegmentGroup, queryParams: Params) { + private upsertModalOutlet(parentOutlet: Outlet, segmentedGroup: UrlSegmentGroup, queryParams: Params, replace = false) { let currentModalOutlet = this.findOutletByModal(this._modalNavigationDepth); // We want to treat every p-r-o as a standalone Outlet. @@ -666,7 +671,7 @@ export class NSLocationStrategy extends LocationStrategy { // tslint:disable-next-line:max-line-length currentModalOutlet = this.createOutlet(outletKey, outletPath, segmentedGroup, parentOutlet, this._modalNavigationDepth, queryParams); this.currentOutlet = currentModalOutlet; - } else if (this.updateStates(currentModalOutlet, segmentedGroup, queryParams)) { + } else if (this.updateStates(currentModalOutlet, segmentedGroup, queryParams, replace)) { this.currentOutlet = currentModalOutlet; // If states updated } } diff --git a/packages/angular/src/lib/legacy/router/ns-location-utils.ts b/packages/angular/src/lib/legacy/router/ns-location-utils.ts index 200f8dd..5267e43 100644 --- a/packages/angular/src/lib/legacy/router/ns-location-utils.ts +++ b/packages/angular/src/lib/legacy/router/ns-location-utils.ts @@ -13,6 +13,7 @@ export interface NavigationOptions { clearHistory?: boolean; animated?: boolean; transition?: NavigationTransition; + replaceUrl?: boolean; } export class Outlet { diff --git a/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts b/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts index 4017d0d..729bee4 100644 --- a/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts +++ b/packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts @@ -255,6 +255,19 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { } } + popCache(outletKey: string) { + const cache = this.cacheByOutlet[outletKey]; + + if (cache) { + if (cache.peek()) { + const state: any = cache.pop()?.state; + if (state?.componentRef) { + destroyComponentRef(state?.componentRef); + } + } + } + } + clearModalCache(outletKey: string) { const cache = this.cacheByOutlet[outletKey]; diff --git a/packages/angular/src/lib/legacy/router/page-router-outlet.ts b/packages/angular/src/lib/legacy/router/page-router-outlet.ts index 93fb85f..715c9d9 100644 --- a/packages/angular/src/lib/legacy/router/page-router-outlet.ts +++ b/packages/angular/src/lib/legacy/router/page-router-outlet.ts @@ -1,7 +1,7 @@ import { Attribute, ChangeDetectorRef, ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, Inject, InjectionToken, Injector, OnDestroy, EventEmitter, Output, Type, ViewContainerRef, ElementRef, InjectFlags, NgZone } from '@angular/core'; import { ActivatedRoute, ActivatedRouteSnapshot, ChildrenOutletContexts, PRIMARY_OUTLET } from '@angular/router'; -import { Frame, Page, NavigatedData, profile } from '@nativescript/core'; +import { Frame, Page, NavigatedData, profile, NavigationEntry } from '@nativescript/core'; import { BehaviorSubject } from 'rxjs'; @@ -241,7 +241,25 @@ export class PageRouterOutlet implements OnDestroy { this._activatedRoute = activatedRoute; this.markActivatedRoute(activatedRoute); - this.locationStrategy._finishBackPageNavigation(this.frame); + // we have a child with the same name, so we don't finish the back nav + if (this.isFinalPageRouterOutlet()) { + this.locationStrategy._finishBackPageNavigation(this.frame); + } + } + + private isFinalPageRouterOutlet() { + let children = this.parentContexts.getContext(this.name)?.children; + while (children) { + const childContext = children.getContext(this.name); + if (!childContext || !childContext.outlet) { + return true; + } + if (childContext.outlet instanceof PageRouterOutlet) { + return false; + } + children = childContext.children; + } + return true; } /** @@ -265,7 +283,9 @@ export class PageRouterOutlet implements OnDestroy { if (NativeScriptDebug.isLogEnabled()) { NativeScriptDebug.routerLog('Currently in page back navigation - component should be reattached instead of activated.'); } - this.locationStrategy._finishBackPageNavigation(this.frame); + if (this.isFinalPageRouterOutlet()) { + this.locationStrategy._finishBackPageNavigation(this.frame); + } } if (NativeScriptDebug.isLogEnabled()) { @@ -364,6 +384,7 @@ export class PageRouterOutlet implements OnDestroy { }); const navOptions = this.locationStrategy._beginPageNavigation(this.frame); + const isReplace = navOptions.replaceUrl && !navOptions.clearHistory; // Clear refCache if navigation with clearHistory if (navOptions.clearHistory) { @@ -377,10 +398,22 @@ export class PageRouterOutlet implements OnDestroy { } }); + page.once(Page.navigatedToEvent, clearCallback); + } else if (navOptions.replaceUrl) { + const clearCallback = () => + setTimeout(() => { + if (this.outlet) { + // potential alternative fix (only fix children of the current outlet) + // const nests = outletKey.split('/'); + // this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.popCache(key)); + this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.popCache(key)); + } + }); + page.once(Page.navigatedToEvent, clearCallback); } - this.frame.navigate({ + const navigationEntry: NavigationEntry = { create() { return page; }, @@ -388,7 +421,13 @@ export class PageRouterOutlet implements OnDestroy { clearHistory: navOptions.clearHistory, animated: navOptions.animated, transition: navOptions.transition, - }); + }; + + if (isReplace && this.frame.currentPage) { + this.frame.replacePage(navigationEntry); + } else { + this.frame.navigate(navigationEntry); + } } // Find and mark the top activated route as an activated one. From 5352b9bf3a8f466ab561cb4b76b7c09790dd9c36 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Mon, 18 Apr 2022 12:39:15 -0300 Subject: [PATCH 6/7] fix: always preserve query params --- .../angular/src/lib/legacy/router/ns-location-strategy.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/angular/src/lib/legacy/router/ns-location-strategy.ts b/packages/angular/src/lib/legacy/router/ns-location-strategy.ts index d6aa7b7..8115cad 100644 --- a/packages/angular/src/lib/legacy/router/ns-location-strategy.ts +++ b/packages/angular/src/lib/legacy/router/ns-location-strategy.ts @@ -558,6 +558,11 @@ export class NSLocationStrategy extends LocationStrategy { } return true; + } else { + if (lastState && equalStateUrls) { + // update query params for last state + lastState.queryParams = { ...queryParams }; + } } return false; From b90fa2fd69494a3534fcade5f347f8c241c8b4a2 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Tue, 31 May 2022 11:16:42 -0700 Subject: [PATCH 7/7] chore: 13.0.4-alpha.1 --- packages/angular/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular/package.json b/packages/angular/package.json index d77decd..b9021f2 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/angular", - "version": "13.0.3", + "version": "13.0.4-alpha.1", "homepage": "https://nativescript.org/", "repository": { "type": "git",