Skip to content

fix(router): fix navigation when clearing history and navigating before the navigatedTo event fires #100

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 2 commits into from
Nov 30, 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
50 changes: 50 additions & 0 deletions packages/angular/src/lib/legacy/router/ns-route-reuse-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface CacheItem {
key: string;
state: DetachedRouteHandle;
isModal: boolean;
markedForDeletion?: boolean;
}

const getSnapshotKey = function (snapshot: ActivatedRouteSnapshot): string {
Expand Down Expand Up @@ -52,6 +53,28 @@ class DetachedStateCache {
}
}

public markCurrentForClear() {
for (const item of this.cache) {
item.markedForDeletion = true;
}
}

public clearMarked() {
// try to preserve same order as .clear()
for (let i = this.cache.length - 1; i >= 0; i--) {
const cacheItem = this.cache[i];
if (cacheItem.markedForDeletion) {
const state = <any>cacheItem.state;
if (!state.componentRef) {
throw new Error('No componentRef found in DetachedRouteHandle');
}

destroyComponentRef(state.componentRef);
this.cache.splice(i, 1);
}
}
}

public clearModalCache() {
let removedItemsCount = 0;
const hasModalPages = this.cache.some((cacheItem) => {
Expand Down Expand Up @@ -259,6 +282,14 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
}
}

markCacheForClear(outletKey: string) {
const cache = this.cacheByOutlet[outletKey];

if (cache) {
cache.markCurrentForClear();
}
}

popCache(outletKey: string) {
const cache = this.cacheByOutlet[outletKey];

Expand All @@ -272,6 +303,25 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
}
}

markCacheForPop(outletKey: string) {
const cache = this.cacheByOutlet[outletKey];

if (cache) {
const item = cache.peek();
if (item) {
item.markedForDeletion = true;
}
}
}

clearMarkedCache(outletKey: string) {
const cache = this.cacheByOutlet[outletKey];

if (cache) {
cache.clearMarked();
}
}

clearModalCache(outletKey: string) {
const cache = this.cacheByOutlet[outletKey];

Expand Down
59 changes: 47 additions & 12 deletions packages/angular/src/lib/legacy/router/page-router-outlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ function isComponentFactoryResolver(item: any): item is ComponentFactoryResolver
return !!item.resolveComponentFactory;
}

function callableOnce<T>(fn: (...args: T[]) => void) {
let called = false;
return (...args: T[]) => {
if (called) {
return;
}
called = true;
return fn(...args);
};
}

export class DestructibleInjector implements Injector {
private refs = new Set<any>();
constructor(private destructibleProviders: ProviderSet, private parent: Injector) {}
Expand Down Expand Up @@ -71,6 +82,10 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
private isEmptyOutlet: boolean;
private viewUtil: ViewUtil;
private frame: Frame;
// this function is used to clear the outlet cache (clear history)
// usually it's cleared in `navigatedTo`, but on quick navigation, the event will be fired after angular already added more things to the cache
// so now we call this if the component is detached or deactivated (meaning it's mid-navigation, before cache manipulation)
private postNavFunction: () => void;

attachEvents: EventEmitter<unknown> = new EventEmitter();
detachEvents: EventEmitter<unknown> = new EventEmitter();
Expand Down Expand Up @@ -209,6 +224,7 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
if (!this.isActivated) {
return;
}
this.postNavFunction?.();

const c = this.activated.instance;
destroyComponentRef(this.activated);
Expand All @@ -234,6 +250,8 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
NativeScriptDebug.routerLog(`PageRouterOutlet.detach() - ${routeToString(this._activatedRoute)}`);
}

this.postNavFunction?.();

// Detach from ChangeDetection
this.activated.hostView.detach();

Expand Down Expand Up @@ -413,28 +431,45 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
this.locationStrategy._beginPageNavigation(this.frame, navOptions);
const isReplace = navOptions.replaceUrl && !navOptions.clearHistory;

const currentRoute = this.activatedRoute;
// Clear refCache if navigation with clearHistory
if (navOptions.clearHistory) {
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.markCacheForClear(key));
const wipeCache = callableOnce(() => {
if (this.postNavFunction === wipeCache) {
this.postNavFunction = null;
}
if (this.outlet && this.activatedRoute === currentRoute) {
// 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.clearMarkedCache(key));
}
});
this.postNavFunction = wipeCache;
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.clearCache(key));
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearCache(key));
}
wipeCache();
});

page.once(Page.navigatedToEvent, clearCallback);
} else if (navOptions.replaceUrl) {
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.markCacheForPop(key));
const popCache = callableOnce(() => {
if (this.postNavFunction === popCache) {
this.postNavFunction = null;
}
if (this.outlet && this.activatedRoute === currentRoute) {
// 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.clearMarkedCache(key));
}
});
this.postNavFunction = popCache;
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));
}
popCache();
});

page.once(Page.navigatedToEvent, clearCallback);
Expand Down