Skip to content

Commit f276f56

Browse files
authored
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 5099170 commit f276f56

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) {}
@@ -71,6 +82,10 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
7182
private isEmptyOutlet: boolean;
7283
private viewUtil: ViewUtil;
7384
private frame: Frame;
85+
// this function is used to clear the outlet cache (clear history)
86+
// usually it's cleared in `navigatedTo`, but on quick navigation, the event will be fired after angular already added more things to the cache
87+
// so now we call this if the component is detached or deactivated (meaning it's mid-navigation, before cache manipulation)
88+
private postNavFunction: () => void;
7489

7590
attachEvents: EventEmitter<unknown> = new EventEmitter();
7691
detachEvents: EventEmitter<unknown> = new EventEmitter();
@@ -209,6 +224,7 @@ export class PageRouterOutlet implements OnDestroy, RouterOutletContract {
209224
if (!this.isActivated) {
210225
return;
211226
}
227+
this.postNavFunction?.();
212228

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

253+
this.postNavFunction?.();
254+
237255
// Detach from ChangeDetection
238256
this.activated.hostView.detach();
239257

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

434+
const currentRoute = this.activatedRoute;
416435
// Clear refCache if navigation with clearHistory
417436
if (navOptions.clearHistory) {
437+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.markCacheForClear(key));
438+
const wipeCache = callableOnce(() => {
439+
if (this.postNavFunction === wipeCache) {
440+
this.postNavFunction = null;
441+
}
442+
if (this.outlet && this.activatedRoute === currentRoute) {
443+
// potential alternative fix (only fix children of the current outlet)
444+
// const nests = outletKey.split('/');
445+
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.clearCache(key));
446+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearMarkedCache(key));
447+
}
448+
});
449+
this.postNavFunction = wipeCache;
418450
const clearCallback = () =>
419451
setTimeout(() => {
420-
if (this.outlet) {
421-
// potential alternative fix (only fix children of the current outlet)
422-
// const nests = outletKey.split('/');
423-
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.clearCache(key));
424-
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearCache(key));
425-
}
452+
wipeCache();
426453
});
427454

428455
page.once(Page.navigatedToEvent, clearCallback);
429456
} else if (navOptions.replaceUrl) {
457+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.markCacheForPop(key));
458+
const popCache = callableOnce(() => {
459+
if (this.postNavFunction === popCache) {
460+
this.postNavFunction = null;
461+
}
462+
if (this.outlet && this.activatedRoute === currentRoute) {
463+
// potential alternative fix (only fix children of the current outlet)
464+
// const nests = outletKey.split('/');
465+
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.popCache(key));
466+
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.clearMarkedCache(key));
467+
}
468+
});
469+
this.postNavFunction = popCache;
430470
const clearCallback = () =>
431471
setTimeout(() => {
432-
if (this.outlet) {
433-
// potential alternative fix (only fix children of the current outlet)
434-
// const nests = outletKey.split('/');
435-
// this.outlet.outletKeys.filter((k) => k.split('/').length >= nests.length).forEach((key) => this.routeReuseStrategy.popCache(key));
436-
this.outlet.outletKeys.forEach((key) => this.routeReuseStrategy.popCache(key));
437-
}
472+
popCache();
438473
});
439474

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

0 commit comments

Comments
 (0)