Skip to content

Commit 7bf7ec6

Browse files
committed
fix(router): ensure navigation via back button works
The router will now navigate and respect the current address value accordingly whenever a popState event is handled. Closes angular#2201
1 parent 60f38ea commit 7bf7ec6

File tree

5 files changed

+99
-14
lines changed

5 files changed

+99
-14
lines changed

modules/angular2/src/router/location.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ export class Location {
3434
this._platformStrategy.onPopState((_) => this._onPopState(_));
3535
}
3636

37-
_onPopState(_): void { ObservableWrapper.callNext(this._subject, {'url': this.path()}); }
37+
_onPopState(_): void {
38+
ObservableWrapper.callNext(this._subject, {'url': this.path(), 'pop': true});
39+
}
3840

3941
path(): string { return this.normalize(this._platformStrategy.path()); }
4042

modules/angular2/src/router/router.ts

+12-9
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export class Router {
100100
* If the given URL begins with a `/`, router will navigate absolutely.
101101
* If the given URL does not begin with `/`, the router will navigate relative to this component.
102102
*/
103-
navigate(url: string): Promise<any> {
103+
navigate(url: string, _skipLocationChange: boolean = false): Promise<any> {
104104
return this._currentNavigation = this._currentNavigation.then((_) => {
105105
this.lastNavigationAttempt = url;
106106
this._startNavigating();
@@ -117,7 +117,7 @@ export class Router {
117117
return this._canDeactivate(matchedInstruction)
118118
.then((result) => {
119119
if (result) {
120-
return this.commit(matchedInstruction)
120+
return this.commit(matchedInstruction, _skipLocationChange)
121121
.then((_) => {
122122
this._emitNavigationFinish(matchedInstruction.accumulatedUrl);
123123
return true;
@@ -180,7 +180,7 @@ export class Router {
180180
/**
181181
* Updates this router and all descendant routers according to the given instruction
182182
*/
183-
commit(instruction: Instruction): Promise<any> {
183+
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
184184
this._currentInstruction = instruction;
185185
if (isPresent(this._outlet)) {
186186
return this._outlet.commit(instruction);
@@ -290,14 +290,17 @@ export class RootRouter extends Router {
290290
hostComponent: Type) {
291291
super(registry, pipeline, null, hostComponent);
292292
this._location = location;
293-
this._location.subscribe((change) => this.navigate(change['url']));
293+
this._location.subscribe((change) => this.navigate(change['url'], isPresent(change['pop'])));
294294
this.registry.configFromComponent(hostComponent, true);
295295
this.navigate(location.path());
296296
}
297297

298-
commit(instruction: Instruction): Promise<any> {
299-
return super.commit(instruction)
300-
.then((_) => { this._location.go(instruction.accumulatedUrl); });
298+
commit(instruction: Instruction, _skipLocationChange: boolean = false): Promise<any> {
299+
var promise = super.commit(instruction);
300+
if (!_skipLocationChange) {
301+
promise = promise.then((_) => { this._location.go(instruction.accumulatedUrl); });
302+
}
303+
return promise;
301304
}
302305
}
303306

@@ -308,9 +311,9 @@ class ChildRouter extends Router {
308311
}
309312

310313

311-
navigate(url: string): Promise<any> {
314+
navigate(url: string, _skipLocationChange: boolean = false): Promise<any> {
312315
// Delegate navigation to the root router
313-
return this.parent.navigate(url);
316+
return this.parent.navigate(url, _skipLocationChange);
314317
}
315318
}
316319

modules/angular2/test/router/location_spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ export function main() {
8888
var locationStrategy = new MockLocationStrategy();
8989
var location = new Location(locationStrategy);
9090

91+
function assertUrl(path) { expect(location.path()).toEqual(path); }
92+
9193
location.go('/ready');
9294
assertUrl('/ready');
9395

@@ -102,8 +104,6 @@ export function main() {
102104

103105
location.back();
104106
assertUrl('/ready');
105-
106-
function assertUrl(path) { expect(location.path()).toEqual(path); }
107107
});
108108
});
109109
}

modules/angular2/test/router/router_integration_spec.ts

+68-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ import {
2727
RouteParams,
2828
Router,
2929
appBaseHrefToken,
30-
routerDirectives
30+
routerDirectives,
31+
HashLocationStrategy
3132
} from 'angular2/router';
3233

3334
import {LocationStrategy} from 'angular2/src/router/location_strategy';
@@ -81,6 +82,57 @@ export function main() {
8182
}));
8283
});
8384

85+
describe('back button app', () => {
86+
beforeEachBindings(() => { return [bind(appComponentTypeToken).toValue(HierarchyAppCmp)]; });
87+
88+
it('should change the url without pushing a new history state for back navigations',
89+
inject([AsyncTestCompleter, TestComponentBuilder], (async, tcb: TestComponentBuilder) => {
90+
91+
tcb.createAsync(HierarchyAppCmp)
92+
.then((rootTC) => {
93+
var router = rootTC.componentInstance.router;
94+
var position = 0;
95+
var flipped = false;
96+
var history =
97+
[
98+
['/parent/child', 'root { parent { hello } }', '/super-parent/child'],
99+
['/super-parent/child', 'root { super-parent { hello2 } }', '/parent/child'],
100+
['/parent/child', 'root { parent { hello } }', false]
101+
]
102+
103+
router.subscribe((_) => {
104+
var location = rootTC.componentInstance.location;
105+
var element = rootTC.nativeElement;
106+
var path = location.path();
107+
108+
var entry = history[position];
109+
110+
expect(path).toEqual(entry[0]);
111+
expect(element).toHaveText(entry[1]);
112+
113+
var nextUrl = entry[2];
114+
if (nextUrl == false) {
115+
flipped = true;
116+
}
117+
118+
if (flipped && position == 0) {
119+
async.done();
120+
return;
121+
}
122+
123+
position = position + (flipped ? -1 : 1);
124+
if (flipped) {
125+
location.back();
126+
} else {
127+
router.navigate(nextUrl);
128+
}
129+
});
130+
131+
router.navigate(history[0][0]);
132+
});
133+
}));
134+
});
135+
84136
describe('hierarchical app', () => {
85137
beforeEachBindings(() => { return [bind(appComponentTypeToken).toValue(HierarchyAppCmp)]; });
86138

@@ -153,6 +205,11 @@ export function main() {
153205
class HelloCmp {
154206
}
155207

208+
@Component({selector: 'hello2-cmp'})
209+
@View({template: 'hello2'})
210+
class Hello2Cmp {
211+
}
212+
156213
@Component({selector: 'app-cmp'})
157214
@View({template: "outer { <router-outlet></router-outlet> }", directives: routerDirectives})
158215
@RouteConfig([new Route({path: '/', component: HelloCmp})])
@@ -166,9 +223,18 @@ class AppCmp {
166223
class ParentCmp {
167224
}
168225

226+
@Component({selector: 'super-parent-cmp'})
227+
@View({template: `super-parent { <router-outlet></router-outlet> }`, directives: routerDirectives})
228+
@RouteConfig([new Route({path: '/child', component: Hello2Cmp})])
229+
class SuperParentCmp {
230+
}
231+
169232
@Component({selector: 'app-cmp'})
170233
@View({template: `root { <router-outlet></router-outlet> }`, directives: routerDirectives})
171-
@RouteConfig([new Route({path: '/parent/...', component: ParentCmp})])
234+
@RouteConfig([
235+
new Route({path: '/parent/...', component: ParentCmp}),
236+
new Route({path: '/super-parent/...', component: SuperParentCmp})
237+
])
172238
class HierarchyAppCmp {
173239
constructor(public router: Router, public location: LocationStrategy) {}
174240
}

modules/angular2/test/router/router_spec.ts

+14
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,20 @@ export function main() {
7676
});
7777
}));
7878

79+
it('should not push a history change on when navigate is called with skipUrlChange',
80+
inject([AsyncTestCompleter], (async) => {
81+
var outlet = makeDummyOutlet();
82+
83+
router.registerOutlet(outlet)
84+
.then((_) => router.config([new Route({path: '/b', component: DummyComponent})]))
85+
.then((_) => router.navigate('/b', true))
86+
.then((_) => {
87+
expect(outlet.spy('commit')).toHaveBeenCalled();
88+
expect(location.urlChanges).toEqual([]);
89+
async.done();
90+
});
91+
}));
92+
7993

8094
it('should navigate after being configured', inject([AsyncTestCompleter], (async) => {
8195
var outlet = makeDummyOutlet();

0 commit comments

Comments
 (0)