Skip to content

fix: resolve routing component cleanup, preserve query params and replaceUrl #69

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nativescript/angular",
"version": "13.0.3",
"version": "13.0.4-alpha.1",
"homepage": "https://nativescript.org/",
"repository": {
"type": "git",
Expand Down
24 changes: 17 additions & 7 deletions packages/angular/src/lib/legacy/router/ns-location-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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}`);
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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());
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface NavigationOptions {
clearHistory?: boolean;
animated?: boolean;
transition?: NavigationTransition;
replaceUrl?: boolean;
}

export class Outlet {
Expand Down
48 changes: 43 additions & 5 deletions packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];

Expand Down
54 changes: 48 additions & 6 deletions packages/angular/src/lib/legacy/router/page-router-outlet.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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()) {
Expand Down Expand Up @@ -364,28 +384,50 @@ 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;
},
context: navigationContext,
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.
Expand Down