Skip to content

Commit ea045c2

Browse files
edusperoniNathanWalker
authored andcommitted
fix(router): fix navigation when clearing history and navigating before the navigatedTo event fires (#100)
* fix(router): fix navigation when clearing history and navigating before the navigatedTo event fires * fix: mark cache for clearing for better consistency
1 parent e613a85 commit ea045c2

File tree

2 files changed

+97
-12
lines changed

2 files changed

+97
-12
lines changed

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ interface CacheItem {
99
key: string;
1010
state: DetachedRouteHandle;
1111
isModal: boolean;
12+
markedForDeletion?: boolean;
1213
}
1314

1415
const getSnapshotKey = function (snapshot: ActivatedRouteSnapshot): string {
@@ -52,6 +53,28 @@ class DetachedStateCache {
5253
}
5354
}
5455

56+
public markCurrentForClear() {
57+
for (const item of this.cache) {
58+
item.markedForDeletion = true;
59+
}
60+
}
61+
62+
public clearMarked() {
63+
// try to preserve same order as .clear()
64+
for (let i = this.cache.length - 1; i >= 0; i--) {
65+
const cacheItem = this.cache[i];
66+
if (cacheItem.markedForDeletion) {
67+
const state = <any>cacheItem.state;
68+
if (!state.componentRef) {
69+
throw new Error('No componentRef found in DetachedRouteHandle');
70+
}
71+
72+
destroyComponentRef(state.componentRef);
73+
this.cache.splice(i, 1);
74+
}
75+
}
76+
}
77+
5578
public clearModalCache() {
5679
let removedItemsCount = 0;
5780
const hasModalPages = this.cache.some((cacheItem) => {
@@ -259,6 +282,14 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
259282
}
260283
}
261284

285+
markCacheForClear(outletKey: string) {
286+
const cache = this.cacheByOutlet[outletKey];
287+
288+
if (cache) {
289+
cache.markCurrentForClear();
290+
}
291+
}
292+
262293
popCache(outletKey: string) {
263294
const cache = this.cacheByOutlet[outletKey];
264295

@@ -272,6 +303,25 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
272303
}
273304
}
274305

306+
markCacheForPop(outletKey: string) {
307+
const cache = this.cacheByOutlet[outletKey];
308+
309+
if (cache) {
310+
const item = cache.peek();
311+
if (item) {
312+
item.markedForDeletion = true;
313+
}
314+
}
315+
}
316+
317+
clearMarkedCache(outletKey: string) {
318+
const cache = this.cacheByOutlet[outletKey];
319+
320+
if (cache) {
321+
cache.clearMarked();
322+
}
323+
}
324+
275325
clearModalCache(outletKey: string) {
276326
const cache = this.cacheByOutlet[outletKey];
277327

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

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,17 @@ function isComponentFactoryResolver(item: any): item is ComponentFactoryResolver
2929
return !!item.resolveComponentFactory;
3030
}
3131

32+
function callableOnce<T>(fn: (...args: T[]) => void) {
33+
let called = false;
34+
return (...args: T[]) => {
35+
if (called) {
36+
return;
37+
}
38+
called = true;
39+
return fn(...args);
40+
};
41+
}
42+
3243
export class DestructibleInjector implements Injector {
3344
private refs = new Set<any>();
3445
constructor(private destructibleProviders: ProviderSet, private parent: Injector) {}
@@ -79,6 +90,10 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
7990
private isEmptyOutlet: boolean;
8091
private viewUtil: ViewUtil;
8192
private frame: Frame;
93+
// this function is used to clear the outlet cache (clear history)
94+
// usually it's cleared in `navigatedTo`, but on quick navigation, the event will be fired after angular already added more things to the cache
95+
// so now we call this if the component is detached or deactivated (meaning it's mid-navigation, before cache manipulation)
96+
private postNavFunction: () => void;
8297

8398
attachEvents: EventEmitter<unknown> = new EventEmitter();
8499
detachEvents: EventEmitter<unknown> = new EventEmitter();
@@ -217,6 +232,7 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
217232
if (!this.isActivated) {
218233
return;
219234
}
235+
this.postNavFunction?.();
220236

221237
const c = this.activated.instance;
222238
destroyComponentRef(this.activated);
@@ -242,6 +258,8 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
242258
NativeScriptDebug.routerLog(`PageRouterOutlet.detach() - ${routeToString(this._activatedRoute)}`);
243259
}
244260

261+
this.postNavFunction?.();
262+
245263
// Detach from ChangeDetection
246264
this.activated.hostView.detach();
247265

@@ -421,28 +439,45 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
421439
this.locationStrategy._beginPageNavigation(this.frame, navOptions);
422440
const isReplace = navOptions.replaceUrl && !navOptions.clearHistory;
423441

442+
const currentRoute = this.activatedRoute;
424443
// Clear refCache if navigation with clearHistory
425444
if (navOptions.clearHistory) {
445+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.markCacheForClear(key));
446+
const wipeCache = callableOnce(() => {
447+
if (this.postNavFunction === wipeCache) {
448+
this.postNavFunction = null;
449+
}
450+
if (this.outlet && this.activatedRoute === currentRoute) {
451+
// potential alternative fix (only fix children of the current outlet)
452+
// const nests = outletKey.split('/');
453+
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.clearCache(key));
454+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearMarkedCache(key));
455+
}
456+
});
457+
this.postNavFunction = wipeCache;
426458
const clearCallback = () =>
427459
setTimeout(() => {
428-
if (this.outlet) {
429-
// potential alternative fix (only fix children of the current outlet)
430-
// const nests = outletKey.split('/');
431-
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.clearCache(key));
432-
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearCache(key));
433-
}
460+
wipeCache();
434461
});
435462

436463
page.once(Page.navigatedToEvent, clearCallback);
437464
} else if (navOptions.replaceUrl) {
465+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.markCacheForPop(key));
466+
const popCache = callableOnce(() => {
467+
if (this.postNavFunction === popCache) {
468+
this.postNavFunction = null;
469+
}
470+
if (this.outlet && this.activatedRoute === currentRoute) {
471+
// potential alternative fix (only fix children of the current outlet)
472+
// const nests = outletKey.split('/');
473+
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.popCache(key));
474+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearMarkedCache(key));
475+
}
476+
});
477+
this.postNavFunction = popCache;
438478
const clearCallback = () =>
439479
setTimeout(() => {
440-
if (this.outlet) {
441-
// potential alternative fix (only fix children of the current outlet)
442-
// const nests = outletKey.split('/');
443-
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.popCache(key));
444-
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.popCache(key));
445-
}
480+
popCache();
446481
});
447482

448483
page.once(Page.navigatedToEvent, clearCallback);

0 commit comments

Comments
 (0)