Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 864c7f0

Browse files
committed
fix($location): avoid unnecessary $locationChange* events due to empty hash
Fixes #16632 Closes #16636
1 parent 2907798 commit 864c7f0

File tree

5 files changed

+52
-19
lines changed

5 files changed

+52
-19
lines changed

src/ng/browser.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ function getHash(url) {
66
return index === -1 ? '' : url.substr(index);
77
}
88

9+
function trimEmptyHash(url) {
10+
return url.replace(/#$/, '');
11+
}
12+
913
/**
1014
* ! This is a private undocumented service !
1115
*
@@ -72,20 +76,21 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
7276
*
7377
* @description
7478
* GETTER:
75-
* Without any argument, this method just returns current value of location.href.
79+
* Without any argument, this method just returns current value of `location.href` (with a
80+
* trailing `#` stripped of if the hash is empty).
7681
*
7782
* SETTER:
7883
* With at least one argument, this method sets url to new value.
79-
* If html5 history api supported, pushState/replaceState is used, otherwise
80-
* location.href/location.replace is used.
81-
* Returns its own instance to allow chaining
84+
* If html5 history api supported, `pushState`/`replaceState` is used, otherwise
85+
* `location.href`/`location.replace` is used.
86+
* Returns its own instance to allow chaining.
8287
*
83-
* NOTE: this api is intended for use only by the $location service. Please use the
88+
* NOTE: this api is intended for use only by the `$location` service. Please use the
8489
* {@link ng.$location $location service} to change url.
8590
*
8691
* @param {string} url New url (when used as setter)
8792
* @param {boolean=} replace Should new url replace current history record?
88-
* @param {object=} state object to use with pushState/replaceState
93+
* @param {object=} state State object to use with `pushState`/`replaceState`
8994
*/
9095
self.url = function(url, replace, state) {
9196
// In modern browsers `history.state` is `null` by default; treating it separately
@@ -143,7 +148,7 @@ function Browser(window, document, $log, $sniffer, $$taskTrackerFactory) {
143148
// - pendingLocation is needed as browsers don't allow to read out
144149
// the new location.href if a reload happened or if there is a bug like in iOS 9 (see
145150
// https://openradar.appspot.com/22186109).
146-
return pendingLocation || location.href;
151+
return trimEmptyHash(pendingLocation || location.href);
147152
}
148153
};
149154

src/ng/location.js

+3-10
Original file line numberDiff line numberDiff line change
@@ -95,17 +95,11 @@ function stripBaseUrl(base, url) {
9595
}
9696
}
9797

98-
9998
function stripHash(url) {
10099
var index = url.indexOf('#');
101100
return index === -1 ? url : url.substr(0, index);
102101
}
103102

104-
function trimEmptyHash(url) {
105-
return url.replace(/#$/, '');
106-
}
107-
108-
109103
function stripFile(url) {
110104
return url.substr(0, stripHash(url).lastIndexOf('/') + 1);
111105
}
@@ -944,7 +938,7 @@ function $LocationProvider() {
944938

945939

946940
// rewrite hashbang url <> html5 url
947-
if (trimEmptyHash($location.absUrl()) !== trimEmptyHash(initialUrl)) {
941+
if ($location.absUrl() !== initialUrl) {
948942
$browser.url($location.absUrl(), true);
949943
}
950944

@@ -963,7 +957,6 @@ function $LocationProvider() {
963957
var oldUrl = $location.absUrl();
964958
var oldState = $location.$$state;
965959
var defaultPrevented;
966-
newUrl = trimEmptyHash(newUrl);
967960
$location.$$parse(newUrl);
968961
$location.$$state = newState;
969962

@@ -991,8 +984,8 @@ function $LocationProvider() {
991984
if (initializing || $location.$$urlUpdatedByLocation) {
992985
$location.$$urlUpdatedByLocation = false;
993986

994-
var oldUrl = trimEmptyHash($browser.url());
995-
var newUrl = trimEmptyHash($location.absUrl());
987+
var oldUrl = $browser.url();
988+
var newUrl = $location.absUrl();
996989
var oldState = $browser.state();
997990
var currentReplace = $location.$$replace;
998991
var urlOrStateChanged = !urlsEqual(oldUrl, newUrl) ||

src/ngMock/angular-mocks.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ angular.mock.$Browser.prototype = {
225225
state = null;
226226
}
227227
if (url) {
228-
this.$$url = url;
228+
// The `$browser` service trims empty hashes; simulate it.
229+
this.$$url = url.replace(/#$/, '');
229230
// Native pushState serializes & copies the object; simulate it.
230231
this.$$state = angular.copy(state);
231232
return this;

test/ng/browserSpecs.js

+34
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,14 @@ describe('browser', function() {
404404
expect(browser.url()).toEqual('https://another.com');
405405
});
406406

407+
it('should strip an empty hash fragment', function() {
408+
fakeWindow.location.href = 'http://test.com#';
409+
expect(browser.url()).toEqual('http://test.com');
410+
411+
fakeWindow.location.href = 'https://another.com#foo';
412+
expect(browser.url()).toEqual('https://another.com#foo');
413+
});
414+
407415
it('should use history.pushState when available', function() {
408416
sniffer.history = true;
409417
browser.url('http://new.org');
@@ -1047,6 +1055,32 @@ describe('browser', function() {
10471055
expect($location.absUrl()).toEqual('http://server/#otherHash');
10481056
});
10491057
});
1058+
1059+
// issue #16632
1060+
it('should not trigger `$locationChangeStart` more than once due to trailing `#`', function() {
1061+
setup({
1062+
history: true,
1063+
html5Mode: true
1064+
});
1065+
1066+
inject(function($flushPendingTasks, $location, $rootScope) {
1067+
$rootScope.$digest();
1068+
1069+
var spy = jasmine.createSpy('$locationChangeStart');
1070+
$rootScope.$on('$locationChangeStart', spy);
1071+
1072+
$rootScope.$evalAsync(function() {
1073+
fakeWindow.location.href += '#';
1074+
});
1075+
$rootScope.$digest();
1076+
1077+
expect(fakeWindow.location.href).toBe('http://server/#');
1078+
expect($location.absUrl()).toBe('http://server/');
1079+
1080+
expect(spy.calls.count()).toBe(0);
1081+
expect(spy).not.toHaveBeenCalled();
1082+
});
1083+
});
10501084
});
10511085

10521086
describe('integration test with $rootScope', function() {

test/ng/locationSpec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ describe('$location', function() {
709709
initService({html5Mode: true, supportHistory: true});
710710
mockUpBrowser({initialUrl: 'http://new.com/#', baseHref: '/'});
711711
inject(function($browser, $location, $window) {
712-
expect($browser.url()).toBe('http://new.com/#');
712+
expect($browser.url()).toBe('http://new.com/');
713713
expect($location.absUrl()).toBe('http://new.com/');
714714
expect($window.location.href).toBe('http://new.com/#');
715715
});

0 commit comments

Comments
 (0)