Skip to content

Commit 2a781de

Browse files
author
vakrilov
committed
refactor(router): Do page navigation with RouteReuseStrategy (#949)
1 parent e5cc581 commit 2a781de

File tree

8 files changed

+244
-144
lines changed

8 files changed

+244
-144
lines changed

Diff for: .gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ tags
2121
.DS_Store
2222
npm-debug.log
2323
nativescript-angular*.tgz
24+
package-lock.json
2425

2526
tests/app/**/*.js
2627
tests/test-output.txt

Diff for: nativescript-angular/router.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import {
55
Optional,
66
SkipSelf,
77
} from "@angular/core";
8-
import { RouterModule, Routes, ExtraOptions } from "@angular/router";
8+
import { RouterModule, Routes, ExtraOptions, RouteReuseStrategy } from "@angular/router";
99
import { LocationStrategy, PlatformLocation } from "@angular/common";
1010
import { Frame } from "tns-core-modules/ui/frame";
1111
import { NSRouterLink } from "./router/ns-router-link";
1212
import { NSRouterLinkActive } from "./router/ns-router-link-active";
1313
import { PageRouterOutlet } from "./router/page-router-outlet";
1414
import { NSLocationStrategy, LocationState } from "./router/ns-location-strategy";
1515
import { NativescriptPlatformLocation } from "./router/ns-platform-location";
16+
import { NsRouteReuseStrategy } from "./router/ns-route-reuse-strategy";
1617
import { RouterExtensions } from "./router/router-extensions";
1718
import { NativeScriptCommonModule } from "./common";
1819

@@ -37,6 +38,8 @@ export type LocationState = LocationState;
3738
NativescriptPlatformLocation,
3839
{ provide: PlatformLocation, useClass: NativescriptPlatformLocation },
3940
RouterExtensions,
41+
NsRouteReuseStrategy,
42+
{ provide: RouteReuseStrategy, useExisting: NsRouteReuseStrategy }
4043
],
4144
imports: [
4245
RouterModule,

Diff for: nativescript-angular/router/ns-location-strategy.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class NSLocationStrategy extends LocationStrategy {
6767

6868
replaceState(state: any, title: string, url: string, queryParams: string): void {
6969
if (this.states.length > 0) {
70-
routerLog("NSLocationStrategy.replaceState changing exisitng state: " +
70+
routerLog("NSLocationStrategy.replaceState changing existing state: " +
7171
`${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
7272
const topState = this.peekState();
7373
topState.state = state;
@@ -104,7 +104,7 @@ export class NSLocationStrategy extends LocationStrategy {
104104
if (state.isPageNavigation) {
105105
// This was a page navigation - so navigate through frame.
106106
routerLog("NSLocationStrategy.back() while not navigating back but top" +
107-
" state is page - will call frame.goback()");
107+
" state is page - will call frame.goBack()");
108108
this.frame.goBack();
109109
} else {
110110
// Nested navigation - just pop the state
+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { Injectable } from "@angular/core";
2+
import { RouteReuseStrategy, ActivatedRouteSnapshot, DetachedRouteHandle } from "@angular/router";
3+
4+
import { routeReuseStrategyLog as log } from "../trace";
5+
import { NSLocationStrategy } from "./ns-location-strategy";
6+
import { pageRouterActivatedSymbol, destroyComponentRef } from "./page-router-outlet";
7+
8+
interface CacheItem {
9+
key: string;
10+
state: DetachedRouteHandle;
11+
}
12+
13+
/**
14+
* Detached state cache
15+
*/
16+
class DetachedStateCache {
17+
private cache = new Array<CacheItem>();
18+
19+
public get length(): number {
20+
return this.cache.length;
21+
}
22+
23+
public push(cacheItem: CacheItem) {
24+
this.cache.push(cacheItem);
25+
}
26+
27+
public pop(): CacheItem {
28+
return this.cache.pop();
29+
}
30+
31+
public peek(): CacheItem {
32+
return this.cache[this.cache.length - 1];
33+
}
34+
35+
public clear() {
36+
log(`DetachedStateCache.clear() ${this.cache.length} items will be destroyed`);
37+
38+
while (this.cache.length > 0) {
39+
const state = <any>this.cache.pop().state;
40+
if (!state.componentRef) {
41+
throw new Error("No componentRed found in DetachedRouteHandle");
42+
}
43+
44+
destroyComponentRef(state.componentRef);
45+
}
46+
}
47+
}
48+
49+
/**
50+
* Does not detach any subtrees. Reuses routes as long as their route config is the same.
51+
*/
52+
@Injectable()
53+
export class NsRouteReuseStrategy implements RouteReuseStrategy {
54+
private cache: DetachedStateCache = new DetachedStateCache();
55+
56+
constructor(private location: NSLocationStrategy) { }
57+
58+
shouldDetach(route: ActivatedRouteSnapshot): boolean {
59+
const key = getSnapshotKey(route);
60+
const isPageActivated = route[pageRouterActivatedSymbol];
61+
const isBack = this.location._isPageNavigatingBack();
62+
const shouldDetach = !isBack && isPageActivated;
63+
64+
log(`shouldDetach isBack: ${isBack} key: ${key} result: ${shouldDetach}`);
65+
66+
return shouldDetach;
67+
}
68+
69+
shouldAttach(route: ActivatedRouteSnapshot): boolean {
70+
const key = getSnapshotKey(route);
71+
const isBack = this.location._isPageNavigatingBack();
72+
const shouldDetach = isBack && this.cache.peek().key === key;
73+
74+
log(`shouldAttach isBack: ${isBack} key: ${key} result: ${shouldDetach}`);
75+
76+
return shouldDetach;
77+
}
78+
79+
80+
store(route: ActivatedRouteSnapshot, state: DetachedRouteHandle): void {
81+
const key = getSnapshotKey(route);
82+
log(`store key: ${key}, state: ${state}`);
83+
84+
if (state) {
85+
this.cache.push({ key, state });
86+
} else {
87+
const topItem = this.cache.peek();
88+
if (topItem.key === key) {
89+
this.cache.pop();
90+
} else {
91+
throw new Error("Trying to pop from DetachedStateCache but keys don't match. " +
92+
`expected: ${topItem.key} actual: ${key}`);
93+
}
94+
}
95+
}
96+
97+
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
98+
const key = getSnapshotKey(route);
99+
const isBack = this.location._isPageNavigatingBack();
100+
const cachedItem = this.cache.peek();
101+
102+
let state = null;
103+
if (isBack && cachedItem && cachedItem.key === key) {
104+
state = cachedItem.state;
105+
}
106+
107+
log(`retrieved isBack: ${isBack} key: ${key} state: ${state}`);
108+
109+
return state;
110+
}
111+
112+
shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
113+
const shouldReuse = future.routeConfig === curr.routeConfig;
114+
115+
if (shouldReuse && curr && curr[pageRouterActivatedSymbol]) {
116+
// When reusing route - copy the pageRouterActivated to the new snapshot
117+
// Its needed in shouldDetach to determine if the route should be detached.
118+
future[pageRouterActivatedSymbol] = curr[pageRouterActivatedSymbol];
119+
}
120+
121+
log(`shouldReuseRoute result: ${shouldReuse}`);
122+
123+
return shouldReuse;
124+
}
125+
126+
clearCache() {
127+
this.cache.clear();
128+
}
129+
}
130+
131+
function getSnapshotKey(snapshot: ActivatedRouteSnapshot): string {
132+
return snapshot.pathFromRoot.join("->");
133+
}

0 commit comments

Comments
 (0)