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

Commit 6172383

Browse files
committed
fix($location): fix handling of hash fragment links
This commit adds a new `fixHashFragmentLinks` method to `$locationProvider`. This method fixes incorrect rewriting of hash fragment links. In order to use in your project, you need to enable the new behavior: ```js myApp.config(function($locationProvider) { $locationProvider.fixHashFragmentLinks(true); }); ``` In addition, you need to inject the `$anchorScroll` service to one of your controllers/services, to enable automatic anchor scrolling. Closes #8675
1 parent 3be6835 commit 6172383

File tree

6 files changed

+99
-6
lines changed

6 files changed

+99
-6
lines changed

src/ng/location.js

+24-6
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ function parseAbsoluteUrl(absoluteUrl, locationObj) {
3131
}
3232

3333

34-
function parseAppUrl(relativeUrl, locationObj) {
34+
function parseAppUrl(relativeUrl, locationObj, fixHashFragmentLinks) {
3535
var prefixed = (relativeUrl.charAt(0) !== '/');
36+
var hashPrefix = (fixHashFragmentLinks && relativeUrl.charAt(0) !== '#') ? '#' : '';
3637
if (prefixed) {
37-
relativeUrl = '/' + relativeUrl;
38+
relativeUrl = '/' + hashPrefix + relativeUrl;
3839
}
3940
var match = urlResolve(relativeUrl);
4041
locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ?
@@ -166,7 +167,7 @@ function LocationHtml5Url(appBase, basePrefix) {
166167
* @param {string} appBase application base URL
167168
* @param {string} hashPrefix hashbang prefix
168169
*/
169-
function LocationHashbangUrl(appBase, hashPrefix) {
170+
function LocationHashbangUrl(appBase, hashPrefix, fixHashFragmentLinks) {
170171
var appBaseNoFile = stripFile(appBase);
171172

172173
parseAbsoluteUrl(appBase, this);
@@ -189,7 +190,7 @@ function LocationHashbangUrl(appBase, hashPrefix) {
189190
throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url,
190191
hashPrefix);
191192
}
192-
parseAppUrl(withoutHashUrl, this);
193+
parseAppUrl(withoutHashUrl, this, fixHashFragmentLinks);
193194

194195
this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase);
195196

@@ -675,7 +676,8 @@ function $LocationProvider() {
675676
enabled: false,
676677
requireBase: true,
677678
rewriteLinks: true
678-
};
679+
},
680+
fixHashFragmentLinks = false;
679681

680682
/**
681683
* @ngdoc method
@@ -736,6 +738,22 @@ function $LocationProvider() {
736738
}
737739
};
738740

741+
/**
742+
* @ngdoc method
743+
* @name $locationProvider#fixHashFragmentLinks
744+
* @description
745+
* @param {boolean=} [enable=false] If set to true, will replace hashes in the URL with double hashes
746+
* @returns {*} current value if used as getter or itself (chaining) if used as setter
747+
*/
748+
this.fixHashFragmentLinks = function(value) {
749+
if (isDefined(value)) {
750+
fixHashFragmentLinks = !!value;
751+
return this;
752+
} else {
753+
return fixHashFragmentLinks;
754+
}
755+
};
756+
739757
/**
740758
* @ngdoc event
741759
* @name $location#$locationChangeStart
@@ -794,7 +812,7 @@ function $LocationProvider() {
794812
appBase = stripHash(initialUrl);
795813
LocationMode = LocationHashbangUrl;
796814
}
797-
$location = new LocationMode(appBase, '#' + hashPrefix);
815+
$location = new LocationMode(appBase, '#' + hashPrefix, fixHashFragmentLinks);
798816
$location.$$parseLinkUrl(initialUrl, initialUrl);
799817

800818
$location.$$state = $browser.state();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Regression test for [#8675](https://github.com/angular/angular.js/issues/8675).
2+
3+
Makes sure that hash fragment links actually jump to the relevant document fragment when `$location`
4+
is injected and configured to operate in hashbang mode. In order to use this fix, you need to inject
5+
the `$anchorScroll` service somewhere in your application, and also add the following config block
6+
to your application:
7+
8+
```js
9+
function($locationProvider) {
10+
$locationProvider.fixHashFragmentLinks(true);
11+
}
12+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html ng-app="test">
3+
<div ng-controller="TestCtrl">
4+
<a id="click-me" href="#some-section">Click me</a>
5+
6+
<div style="height:9999px;"></div>
7+
8+
<h1 id="some-section" style="height:500px">Should scroll here</h1>
9+
</div>
10+
11+
<script src="/build/angular.js"></script>
12+
<script src="script.js"></script>
13+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
angular.module("test", [])
2+
.config(function($locationProvider) {
3+
$locationProvider.fixHashFragmentLinks(true);
4+
})
5+
.controller("TestCtrl", function($scope, $anchorScroll) {
6+
// $anchorScroll is required for handling automatic scrolling for hash fragment links
7+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
describe('Hash Fragment Scrolling', function () {
2+
beforeEach(function () {
3+
loadFixture("ng/location/hashFragmentScrolling").andWaitForAngular();
4+
});
5+
6+
it('should scroll to the element whose id appears in the hash part of the link', function () {
7+
var initialScrollTop = null;
8+
browser.executeScript('return document.body.scrollTop;').then(function (scrollTop) {
9+
initialScrollTop = scrollTop;
10+
});
11+
element(by.id('click-me')).click();
12+
expect(browser.executeScript('return document.body.scrollTop;')).toBeGreaterThan(initialScrollTop);
13+
expect(browser.executeScript('return document.location.hash;')).toBe('##some-section');
14+
});
15+
});

test/ng/locationSpec.js

+28
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,34 @@ describe('$location', function() {
21512151
expect(location.absUrl()).toBe('http://server/pre/index.html#/not-starting-with-slash');
21522152
});
21532153

2154+
describe('fixHashFragmentLinks(true)',function() {
2155+
it("should prefix hash url with another hash sign if it did not start with a /", function () {
2156+
location = new LocationHashbangUrl('http://server/pre/index.html', '#', true);
2157+
2158+
location.$$parse('http://server/pre/index.html#not-starting-with-slash');
2159+
expect(location.url()).toBe('#not-starting-with-slash');
2160+
expect(location.path()).toBe('');
2161+
expect(location.hash()).toBe('not-starting-with-slash');
2162+
expect(location.absUrl()).toBe('http://server/pre/index.html##not-starting-with-slash');
2163+
});
2164+
2165+
it("should not modify the url is it already starts with a /", function () {
2166+
location = new LocationHashbangUrl('http://server/pre/index.html', '#', true);
2167+
2168+
location.$$parse('http://server/pre/index.html#/starting-with-slash');
2169+
expect(location.url()).toBe('/starting-with-slash');
2170+
expect(location.path()).toBe('/starting-with-slash');
2171+
expect(location.hash()).toBe('');
2172+
expect(location.absUrl()).toBe('http://server/pre/index.html#/starting-with-slash');
2173+
});
2174+
2175+
it("should not append another hash sign if the existing url already starts with a hash sign", function () {
2176+
location = new LocationHashbangUrl('http://server/pre/index.html', '#', true);
2177+
location.$$parse('http://server/pre/index.html##digesting-unicorn');
2178+
expect(location.path()).toBe('');
2179+
expect(location.hash()).toBe('digesting-unicorn');
2180+
});
2181+
});
21542182

21552183
it('should not strip stuff from path just because it looks like Windows drive when it\'s not',
21562184
function() {

0 commit comments

Comments
 (0)