Skip to content

Commit b8cee56

Browse files
MartoYankovADjenkov
authored and
ADjenkov
committed
feat(router): rework to support history by outlet
1 parent 7cc08ce commit b8cee56

File tree

3 files changed

+121
-46
lines changed

3 files changed

+121
-46
lines changed

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

+94-35
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Injectable } from "@angular/core";
22
import { LocationStrategy } from "@angular/common";
3+
import { DefaultUrlSerializer, UrlSegmentGroup, UrlTree } from "@angular/router";
34
import { routerLog } from "../trace";
45
import { NavigationTransition } from "tns-core-modules/ui/frame";
56
import { isPresent } from "../lang-facade";
@@ -21,12 +22,15 @@ export interface LocationState {
2122
title: string;
2223
url: string;
2324
queryParams: string;
25+
segmentGroup: UrlSegmentGroup;
2426
isPageNavigation: boolean;
2527
}
2628

2729
@Injectable()
2830
export class NSLocationStrategy extends LocationStrategy {
29-
private states = new Array<LocationState>();
31+
private statesByOutlet: { [key: string]: Array<LocationState> } = {};
32+
private currentUrlTree: UrlTree;
33+
private currentOutlet: string;
3034
private popStateCallbacks = new Array<(_: any) => any>();
3135

3236
private _isPageNavigationBack = false;
@@ -38,8 +42,17 @@ export class NSLocationStrategy extends LocationStrategy {
3842
}
3943

4044
path(): string {
41-
let state = this.peekState();
42-
const result = state ? state.url : "/";
45+
if (!this.currentUrlTree || !this.currentOutlet) {
46+
return "/";
47+
}
48+
49+
const state = this.peekState(this.currentOutlet);
50+
51+
this.currentUrlTree.root.children[this.currentOutlet] = state.segmentGroup;
52+
53+
const urlSerializer = new DefaultUrlSerializer();
54+
const url = urlSerializer.serialize(this.currentUrlTree);
55+
const result = url;
4356
routerLog("NSLocationStrategy.path(): " + result);
4457
return result;
4558
}
@@ -56,25 +69,55 @@ export class NSLocationStrategy extends LocationStrategy {
5669
}
5770

5871
pushStateInternal(state: any, title: string, url: string, queryParams: string): void {
59-
let isNewPage = this.states.length === 0;
60-
this.states.push({
61-
state: state,
62-
title: title,
63-
url: url,
64-
queryParams: queryParams,
65-
isPageNavigation: isNewPage
72+
const urlSerializer = new DefaultUrlSerializer();
73+
const stateUrlTree: UrlTree = urlSerializer.parse(url);
74+
const rootOutlets = stateUrlTree.root.children;
75+
76+
this.currentUrlTree = stateUrlTree;
77+
78+
Object.keys(rootOutlets).forEach(outletName => {
79+
const outletStates = this.statesByOutlet[outletName] = this.statesByOutlet[outletName] || [];
80+
const isNewPage = outletStates.length === 0;
81+
const lastState = this.peekState(outletName);
82+
83+
if (!lastState || lastState.toString() !== rootOutlets[outletName].toString()) {
84+
outletStates.push({
85+
state: state,
86+
title: title,
87+
url: url,
88+
queryParams: queryParams,
89+
segmentGroup: rootOutlets[outletName],
90+
isPageNavigation: isNewPage
91+
});
92+
}
6693
});
6794
}
6895

6996
replaceState(state: any, title: string, url: string, queryParams: string): void {
70-
if (this.states.length > 0) {
97+
const states = this.statesByOutlet[this.currentOutlet];
98+
if (states && states.length > 0) {
7199
routerLog("NSLocationStrategy.replaceState changing existing state: " +
72100
`${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
73-
const topState = this.peekState();
74-
topState.state = state;
75-
topState.title = title;
76-
topState.url = url;
77-
topState.queryParams = queryParams;
101+
102+
103+
// !!!!! In all the e2e tests this method replaces the url with the same url
104+
// !!!!! making it obsolete. The only scenario is the initial call that is handled
105+
// !!!!! in the else statement.
106+
107+
// const urlSerializer = new DefaultUrlSerializer();
108+
// const stateUrlTree: UrlTree = urlSerializer.parse(url);
109+
// const rootOutlets = stateUrlTree.root.children;
110+
111+
// Object.keys(rootOutlets).forEach(outletName => {
112+
// const outletStates = this.statesByOutlet[outletName] = this.statesByOutlet[outletName] || [];
113+
// const topState = this.peekState(outletName);
114+
115+
// topState.segmentGroup = rootOutlets[outletName];
116+
// topState.state = state;
117+
// topState.title = title;
118+
// topState.url = url;
119+
// topState.queryParams = queryParams;
120+
// });
78121
} else {
79122
routerLog("NSLocationStrategy.replaceState pushing new state: " +
80123
`${state}, title: ${title}, url: ${url}, queryParams: ${queryParams}`);
@@ -88,20 +131,21 @@ export class NSLocationStrategy extends LocationStrategy {
88131

89132
back(): void {
90133
if (this._isPageNavigationBack) {
134+
const states = this.statesByOutlet[this.currentOutlet];
91135
// We are navigating to the previous page
92136
// clear the stack until we get to a page navigation state
93-
let state = this.states.pop();
137+
let state = states.pop();
94138
let count = 1;
95139

96140
while (!(state.isPageNavigation)) {
97-
state = this.states.pop();
141+
state = states.pop();
98142
count++;
99143
}
100144

101145
routerLog("NSLocationStrategy.back() while navigating back. States popped: " + count);
102146
this.callPopState(state, true);
103147
} else {
104-
let state = this.peekState();
148+
let state = this.peekState(this.currentOutlet);
105149
if (state.isPageNavigation) {
106150
// This was a page navigation - so navigate through frame.
107151
routerLog("NSLocationStrategy.back() while not navigating back but top" +
@@ -112,14 +156,14 @@ export class NSLocationStrategy extends LocationStrategy {
112156
// Nested navigation - just pop the state
113157
routerLog("NSLocationStrategy.back() while not navigating back but top" +
114158
" state is not page - just pop");
115-
this.callPopState(this.states.pop(), true);
159+
this.callPopState(this.statesByOutlet[this.currentOutlet].pop(), true);
116160
}
117161
}
118162

119163
}
120164

121165
canGoBack() {
122-
return this.states.length > 1;
166+
return this.statesByOutlet[this.currentOutlet].length > 1;
123167
}
124168

125169
onPopState(fn: (_: any) => any): void {
@@ -133,33 +177,46 @@ export class NSLocationStrategy extends LocationStrategy {
133177
}
134178

135179
private callPopState(state: LocationState, pop: boolean = true) {
136-
const change = { url: state.url, pop: pop };
180+
const urlSerializer = new DefaultUrlSerializer();
181+
const url = urlSerializer.serialize(this.currentUrlTree);
182+
const change = { url: url, pop: pop };
137183
for (let fn of this.popStateCallbacks) {
138184
fn(change);
139185
}
140186
}
141187

142-
private peekState(): LocationState {
143-
if (this.states.length > 0) {
144-
return this.states[this.states.length - 1];
188+
private peekState(name: string) {
189+
const states = this.statesByOutlet[name] || [];
190+
if (states.length > 0) {
191+
return states[states.length - 1];
145192
}
146193
return null;
147194
}
148195

149196
public toString() {
150-
return this.states
151-
.map((v, i) => `${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}] "${v.url}"`)
152-
.reverse()
153-
.join("\n");
197+
let result = [];
198+
199+
Object.keys(this.statesByOutlet).forEach(outletName => {
200+
const outletStates = this.statesByOutlet[outletName];
201+
const outletLog = outletStates
202+
.map((v, i) => `${i}.[${v.isPageNavigation ? "PAGE" : "INTERNAL"}] "${v.segmentGroup.toString()}"`)
203+
.reverse();
204+
205+
result.push(`${outletName} :`);
206+
result = result.concat(result, outletLog);
207+
});
208+
209+
return result.join("\n");
154210
}
155211

156212
// Methods for syncing with page navigation in PageRouterOutlet
157-
public _beginBackPageNavigation() {
213+
public _beginBackPageNavigation(name: string) {
158214
routerLog("NSLocationStrategy.startGoBack()");
159215
if (this._isPageNavigationBack) {
160216
throw new Error("Calling startGoBack while going back.");
161217
}
162218
this._isPageNavigationBack = true;
219+
this.currentOutlet = name;
163220
}
164221

165222
public _finishBackPageNavigation() {
@@ -174,17 +231,19 @@ export class NSLocationStrategy extends LocationStrategy {
174231
return this._isPageNavigationBack;
175232
}
176233

177-
public _beginPageNavigation(): NavigationOptions {
234+
public _beginPageNavigation(name: string): NavigationOptions {
178235
routerLog("NSLocationStrategy._beginPageNavigation()");
179-
const lastState = this.peekState();
236+
const lastState = this.peekState(name);
180237
if (lastState) {
181238
lastState.isPageNavigation = true;
182239
}
183240

241+
this.currentOutlet = name;
242+
184243
const navOptions = this._currentNavigationOptions || defaultNavOptions;
185244
if (navOptions.clearHistory) {
186245
routerLog("NSLocationStrategy._beginPageNavigation clearing states history");
187-
this.states = [lastState];
246+
this.statesByOutlet[name] = [lastState];
188247
}
189248

190249
this._currentNavigationOptions = undefined;
@@ -201,7 +260,7 @@ export class NSLocationStrategy extends LocationStrategy {
201260
`${JSON.stringify(this._currentNavigationOptions)})`);
202261
}
203262

204-
public _getStates(): Array<LocationState> {
205-
return this.states.slice();
263+
public _getStates(): { [key: string]: Array<LocationState> } {
264+
return this.statesByOutlet;
206265
}
207266
}

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

+24-8
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ class DetachedStateCache {
5757
*/
5858
@Injectable()
5959
export class NSRouteReuseStrategy implements RouteReuseStrategy {
60-
private cache: DetachedStateCache = new DetachedStateCache();
60+
private cacheByOutlet: { [key: string]: DetachedStateCache } = {};
6161

6262
constructor(private location: NSLocationStrategy) { }
6363

@@ -77,9 +77,14 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
7777
shouldAttach(route: ActivatedRouteSnapshot): boolean {
7878
route = findTopActivatedRouteNodeForOutlet(route);
7979

80+
const cache = this.cacheByOutlet[route.outlet];
81+
if (!cache) {
82+
return false;
83+
}
84+
8085
const key = getSnapshotKey(route);
8186
const isBack = this.location._isPageNavigatingBack();
82-
const shouldAttach = isBack && this.cache.peek().key === key;
87+
const shouldAttach = isBack && cache.peek().key === key;
8388

8489
log(`shouldAttach isBack: ${isBack} key: ${key} result: ${shouldAttach}`);
8590

@@ -92,12 +97,14 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
9297
const key = getSnapshotKey(route);
9398
log(`store key: ${key}, state: ${state}`);
9499

100+
const cache = this.cacheByOutlet[route.outlet] = this.cacheByOutlet[route.outlet] || new DetachedStateCache();
101+
95102
if (state) {
96-
this.cache.push({ key, state });
103+
cache.push({ key, state });
97104
} else {
98-
const topItem = this.cache.peek();
105+
const topItem = cache.peek();
99106
if (topItem.key === key) {
100-
this.cache.pop();
107+
cache.pop();
101108
} else {
102109
throw new Error("Trying to pop from DetachedStateCache but keys don't match. " +
103110
`expected: ${topItem.key} actual: ${key}`);
@@ -108,9 +115,14 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
108115
retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null {
109116
route = findTopActivatedRouteNodeForOutlet(route);
110117

118+
const cache = this.cacheByOutlet[route.outlet];
119+
if (!cache) {
120+
return null;
121+
}
122+
111123
const key = getSnapshotKey(route);
112124
const isBack = this.location._isPageNavigatingBack();
113-
const cachedItem = this.cache.peek();
125+
const cachedItem = cache.peek();
114126

115127
let state = null;
116128
if (isBack && cachedItem && cachedItem.key === key) {
@@ -136,8 +148,12 @@ export class NSRouteReuseStrategy implements RouteReuseStrategy {
136148
return shouldReuse;
137149
}
138150

139-
clearCache() {
140-
this.cache.clear();
151+
clearCache(outletName: string) {
152+
const cache = this.cacheByOutlet[outletName];
153+
154+
if (cache) {
155+
cache.clear();
156+
}
141157
}
142158
}
143159

Diff for: nativescript-angular/router/page-router-outlet.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -304,17 +304,17 @@ export class PageRouterOutlet implements OnDestroy, OnInit { // tslint:disable-l
304304

305305
page.on(Page.navigatedFromEvent, (<any>global).Zone.current.wrap((args: NavigatedData) => {
306306
if (args.isBackNavigation) {
307-
this.locationStrategy._beginBackPageNavigation();
307+
this.locationStrategy._beginBackPageNavigation(this.name);
308308
this.locationStrategy.back();
309309
}
310310
}));
311311

312-
const navOptions = this.locationStrategy._beginPageNavigation();
312+
const navOptions = this.locationStrategy._beginPageNavigation(this.name);
313313

314314
// Clear refCache if navigation with clearHistory
315315
if (navOptions.clearHistory) {
316316
const clearCallback = () => setTimeout(() => {
317-
this.routeReuseStrategy.clearCache();
317+
this.routeReuseStrategy.clearCache(this.name);
318318
page.off(Page.navigatedToEvent, clearCallback);
319319
});
320320

0 commit comments

Comments
 (0)