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", 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..8115cad 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. @@ -553,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; @@ -649,7 +659,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 +676,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 cc645e8..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 @@ -120,12 +120,38 @@ 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 { outlet, outletKey } = this.findValidOutletAndKey(route); const cache = this.cacheByOutlet[outletKey]; if (!cache) { return false; @@ -154,7 +180,7 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy { NativeScriptDebug.routeReuseStrategyLog(`store key: ${key}, state: ${state}`); } - const outletKey = this.location.getRouteFullPath(route); + const { outletKey } = this.findValidOutletAndKey(route); // tslint:disable-next-line:max-line-length const cache = (this.cacheByOutlet[outletKey] = this.cacheByOutlet[outletKey] || new DetachedStateCache()); @@ -183,8 +209,7 @@ 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 { outlet, outletKey } = this.findValidOutletAndKey(route); const cache = this.cacheByOutlet[outletKey]; if (!cache) { return null; @@ -230,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 29e9585..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,20 +384,36 @@ 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) { 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)); + } + }); + + 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; }, @@ -385,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.