From 595bcdd1ac922fcdc52e50e057d8d85fc0a704d3 Mon Sep 17 00:00:00 2001 From: Dimitrios Loukadakis Date: Sun, 8 May 2016 04:24:46 +0300 Subject: [PATCH] fix(router): browser back and forward buttons not working correctly. Closes #8524 Closes #8532 --- .../@angular/common/testing/location_mock.ts | 44 ++++++++++++++----- modules/@angular/router/src/router.ts | 28 ++++++------ .../@angular/router/test/integration_spec.ts | 19 ++++++++ 3 files changed, 67 insertions(+), 24 deletions(-) diff --git a/modules/@angular/common/testing/location_mock.ts b/modules/@angular/common/testing/location_mock.ts index d42ed60111..85d05ab4eb 100644 --- a/modules/@angular/common/testing/location_mock.ts +++ b/modules/@angular/common/testing/location_mock.ts @@ -9,19 +9,19 @@ import {Location} from '../index'; export class SpyLocation implements Location { urlChanges: string[] = []; /** @internal */ - _path: string = ''; + private _history: LocationState[] = [new LocationState('', '')]; /** @internal */ - _query: string = ''; + private _historyIndex: number = 0; /** @internal */ _subject: EventEmitter = new EventEmitter(); /** @internal */ _baseHref: string = ''; - setInitialPath(url: string) { this._path = url; } + setInitialPath(url: string) { this._history[this._historyIndex].path = url; } setBaseHref(url: string) { this._baseHref = url; } - path(): string { return this._path; } + path(): string { return this._history[this._historyIndex].path; } simulateUrlPop(pathname: string) { ObservableWrapper.callEmit(this._subject, {'url': pathname, 'pop': true}); @@ -43,11 +43,17 @@ export class SpyLocation implements Location { go(path: string, query: string = '') { path = this.prepareExternalUrl(path); - if (this._path == path && this._query == query) { + + if (this._historyIndex > 0) { + this._history.splice(this._historyIndex + 1); + } + this._history.push(new LocationState(path, query)); + this._historyIndex = this._history.length - 1; + + var locationState = this._history[this._historyIndex - 1]; + if(locationState.path == path && locationState.query == query) { return; } - this._path = path; - this._query = query; var url = path + (query.length > 0 ? ('?' + query) : ''); this.urlChanges.push(url); @@ -55,19 +61,26 @@ export class SpyLocation implements Location { replaceState(path: string, query: string = '') { path = this.prepareExternalUrl(path); - this._path = path; - this._query = query; + + this._history[this._historyIndex].path = path; + this._history[this._historyIndex].query = query; var url = path + (query.length > 0 ? ('?' + query) : ''); this.urlChanges.push('replace: ' + url); } forward() { - // TODO + if (this._historyIndex < (this._history.length - 1)) { + this._historyIndex++; + ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true}) + } } back() { - // TODO + if (this._historyIndex > 0) { + this._historyIndex--; + ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true}) + } } subscribe(onNext: (value: any) => void, onThrow: (error: any) => void = null, @@ -79,3 +92,12 @@ export class SpyLocation implements Location { platformStrategy: any = null; normalize(url: string): string { return null; } } + +class LocationState { + path: string; + query: string; + constructor(path: string, query: string) { + this.path = path; + this.query = query; + } +} diff --git a/modules/@angular/router/src/router.ts b/modules/@angular/router/src/router.ts index 57f721e7ae..3b203e85af 100644 --- a/modules/@angular/router/src/router.ts +++ b/modules/@angular/router/src/router.ts @@ -148,23 +148,25 @@ export class Router { private _setUpLocationChangeListener(): void { this._locationSubscription = this._location.subscribe( - (change) => { this._navigate(this._urlSerializer.parse(change['url'])); }); + (change) => { this._navigate(this._urlSerializer.parse(change['url']), change['pop']); }); } - private _navigate(url: UrlTree): Promise { + private _navigate(url: UrlTree, pop?: boolean): Promise { this._urlTree = url; return recognize(this._componentResolver, this._rootComponentType, url, this._routeTree) - .then(currTree => { - return new _ActivateSegments(currTree, this._routeTree) - .activate(this._routerOutletMap, this._rootComponent) - .then(updated => { - if (updated) { - this._routeTree = currTree; - this._location.go(this._urlSerializer.serialize(this._urlTree)); - this._changes.emit(null); - } - }); - }); + .then(currTree => { + return new _ActivateSegments(currTree, this._routeTree) + .activate(this._routerOutletMap, this._rootComponent) + .then(updated => { + if (updated) { + this._routeTree = currTree; + if (isBlank(pop) || !pop) { + this._location.go(this._urlSerializer.serialize(this._urlTree)); + } + this._changes.emit(null); + } + }); + }); } } diff --git a/modules/@angular/router/test/integration_spec.ts b/modules/@angular/router/test/integration_spec.ts index 4dc6230105..5ae75f15a7 100644 --- a/modules/@angular/router/test/integration_spec.ts +++ b/modules/@angular/router/test/integration_spec.ts @@ -62,6 +62,25 @@ export function main() { expect(location.path()).toEqual('/team/33/simple'); }))); + it('should navigate back and forward', + fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { + let fixture = tcb.createFakeAsync(RootCmp); + + router.navigateByUrl('/team/33/simple'); + advance(fixture); + + router.navigateByUrl('/team/22/user/victor'); + advance(fixture); + + location.back(); + advance(fixture); + expect(location.path()).toEqual('/team/33/simple'); + + location.forward(); + advance(fixture); + expect(location.path()).toEqual('/team/22/user/victor'); + }))); + it('should navigate when locations changes', fakeAsync(inject([Router, TestComponentBuilder, Location], (router, tcb, location) => { let fixture = tcb.createFakeAsync(RootCmp);