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

Commit dc3de7f

Browse files
committed
feat($location): add ability to opt-out of <base/> tag requirement in html5Mode
This feature allows disabling Angular's requirement of using a <base/> tag when using location in html5Mode, for applications that do not require using $location in html5Mode in IE9. To accomplish this, the $locationProvider.html5Mode method has been changed to accept a definition object which can optionally set a requireBase property to false, removing the requirement of a <base> tag being present when html5Mode is enabled. BREAKING CHANGE: The $location.html5Mode API has changed to allow enabling html5Mode by passing an object (as well as still supporting passing a boolean). Symmetrically, the method now returns an object instead of a boolean value. To migrate, follow the code example below: Before: var mode = $locationProvider.html5Mode(); After: var mode = $locationProvider.html5Mode().enabled; Fixes #8934
1 parent ace40d5 commit dc3de7f

File tree

4 files changed

+155
-15
lines changed

4 files changed

+155
-15
lines changed

docs/content/error/$location/nobase.ngdoc

+13-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,19 @@
44
@description
55

66
If you configure {@link ng.$location `$location`} to use
7-
{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [`<base href="">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag.
7+
{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [`<base href="">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag or configure
8+
`$locationProvider` to not require a base tag by passing a definition object with
9+
`requireBase:false` to `$locationProvider.html5Mode()`:
10+
11+
```javascript
12+
$locationProvider.html5Mode({
13+
enabled: true,
14+
requireBase: false
15+
});
16+
```
17+
18+
Note that removing the requirement for a <base> tag will have adverse side effects when resolving
19+
relative paths with `$location` in IE9.
820

921
The base URL is then used to resolve all relative URLs throughout the application regardless of the
1022
entry point into the app.

docs/content/guide/$location.ngdoc

+10-7
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,11 @@ To configure the `$location` service, retrieve the
9191
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
9292

9393

94-
- **html5Mode(mode)**: {boolean}<br />
95-
`true` - see HTML5 mode<br />
96-
`false` - see Hashbang mode<br />
97-
default: `false`
94+
- **html5Mode(mode)**: {boolean|Object}<br />
95+
`true` or `enabled:true` - see HTML5 mode<br />
96+
`false` or `enabled:false` - see Hashbang mode<br />
97+
`requireBase:true` - see Relative links<br />
98+
default: `enabled:false`
9899

99100
- **hashPrefix(prefix)**: {string}<br />
100101
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
@@ -328,9 +329,11 @@ reload to the original link.
328329

329330
### Relative links
330331

331-
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url base in
332-
the head of your main html file (`<base href="/my-base">`). With that, relative urls will
333-
always be resolved to this base url, event if the initial url of the document was different.
332+
Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url
333+
base in the head of your main html file (`<base href="/my-base">`) unless `html5Mode.requireBase` is
334+
set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
335+
that, relative urls will always be resolved to this base url, event if the initial url of the
336+
document was different.
334337

335338
There is one exception: Links that only contain a hash fragment (e.g. `<a href="#target">`)
336339
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling

src/ng/location.js

+28-7
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,10 @@ function locationGetterSetter(property, preprocess) {
584584
*/
585585
function $LocationProvider(){
586586
var hashPrefix = '',
587-
html5Mode = false;
587+
html5Mode = {
588+
enabled: false,
589+
requireBase: true
590+
};
588591

589592
/**
590593
* @ngdoc method
@@ -606,12 +609,30 @@ function $LocationProvider(){
606609
* @ngdoc method
607610
* @name $locationProvider#html5Mode
608611
* @description
609-
* @param {boolean=} mode Use HTML5 strategy if available.
610-
* @returns {*} current value if used as getter or itself (chaining) if used as setter
612+
* @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
613+
* If object, sets `enabled` and `requireBase` to respective values.
614+
* - **enabled** – `{boolean}` – Sets `html5Mode.enabled`. If true, will rely on
615+
* `history.pushState` to change urls where supported. Will fall back to hash-prefixed paths
616+
* in browsers that do not support `pushState`.
617+
* - **requireBase** - `{boolean}` - Sets `html5Mode.requireBase` (default: `true`). When
618+
* html5Mode is enabled, specifies whether or not a <base> tag is required to be present. If
619+
* `enabled` and `requireBase` are true, and a base tag is not present, an error will be
620+
* thrown when `$location` is injected. See the
621+
* {@link guide/$location $location guide for more information}
622+
*
623+
* @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
611624
*/
612625
this.html5Mode = function(mode) {
613-
if (isDefined(mode)) {
614-
html5Mode = mode;
626+
if (isBoolean(mode)) {
627+
html5Mode.enabled = mode;
628+
return this;
629+
} else if (isObject(mode)) {
630+
html5Mode.enabled = isBoolean(mode.enabled) ?
631+
mode.enabled :
632+
html5Mode.enabled;
633+
html5Mode.requireBase = isBoolean(mode.requireBase) ?
634+
mode.requireBase :
635+
html5Mode.requireBase;
615636
return this;
616637
} else {
617638
return html5Mode;
@@ -653,8 +674,8 @@ function $LocationProvider(){
653674
initialUrl = $browser.url(),
654675
appBase;
655676

656-
if (html5Mode) {
657-
if (!baseHref) {
677+
if (html5Mode.enabled) {
678+
if (!baseHref && html5Mode.requireBase) {
658679
throw $locationMinErr('nobase',
659680
"$location in HTML5 mode requires a <base> tag to be present!");
660681
}

test/ng/locationSpec.js

+104
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,77 @@ describe('$location', function() {
16391639
return undefined;
16401640
}
16411641

1642+
1643+
describe('html5Mode', function() {
1644+
it('should set enabled and requireBase when called with object', function() {
1645+
module(function($locationProvider) {
1646+
expect($locationProvider.html5Mode()).toEqual({
1647+
enabled: false,
1648+
requireBase: true
1649+
});
1650+
});
1651+
1652+
inject(function(){});
1653+
});
1654+
1655+
1656+
it('should only overwrite existing properties if values are boolean', function() {
1657+
module(function($locationProvider) {
1658+
$locationProvider.html5Mode({
1659+
enabled: 'duh',
1660+
requireBase: 'probably'
1661+
});
1662+
1663+
expect($locationProvider.html5Mode()).toEqual({
1664+
enabled: false,
1665+
requireBase: true
1666+
});
1667+
});
1668+
1669+
inject(function(){});
1670+
});
1671+
1672+
1673+
it('should not set unknown input properties to html5Mode object', function() {
1674+
module(function($locationProvider) {
1675+
$locationProvider.html5Mode({
1676+
someProp: 'foo'
1677+
});
1678+
1679+
expect($locationProvider.html5Mode()).toEqual({
1680+
enabled: false,
1681+
requireBase: true
1682+
});
1683+
});
1684+
1685+
inject(function(){});
1686+
});
1687+
1688+
1689+
it('should default to enabled:false and requireBase:true', function() {
1690+
module(function($locationProvider) {
1691+
expect($locationProvider.html5Mode()).toEqual({
1692+
enabled: false,
1693+
requireBase: true
1694+
});
1695+
});
1696+
1697+
inject(function(){});
1698+
});
1699+
1700+
1701+
it('should return html5Mode object when called without value', function() {
1702+
module(function($locationProvider) {
1703+
expect($locationProvider.html5Mode()).toEqual({
1704+
enabled: false,
1705+
requireBase: true
1706+
});
1707+
});
1708+
1709+
inject(function(){});
1710+
});
1711+
});
1712+
16421713
describe('LocationHtml5Url', function() {
16431714
var location, locationIndex;
16441715

@@ -1661,6 +1732,39 @@ describe('$location', function() {
16611732
// Note: relies on the previous state!
16621733
expect(parseLinkAndReturn(location, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://server/pre/otherPath#test');
16631734
});
1735+
1736+
1737+
it('should complain if no base tag present', function() {
1738+
module(function($locationProvider) {
1739+
$locationProvider.html5Mode(true);
1740+
});
1741+
1742+
inject(function($browser, $injector) {
1743+
$browser.$$baseHref = undefined;
1744+
expect(function() {
1745+
$injector.get('$location');
1746+
}).toThrowMinErr('$location', 'nobase',
1747+
"$location in HTML5 mode requires a <base> tag to be present!");
1748+
});
1749+
});
1750+
1751+
1752+
it('should not complain if baseOptOut set to true in html5Mode', function() {
1753+
module(function($locationProvider) {
1754+
$locationProvider.html5Mode({
1755+
enabled: true,
1756+
requireBase: false
1757+
});
1758+
});
1759+
1760+
inject(function($browser, $injector) {
1761+
$browser.$$baseHref = undefined;
1762+
expect(function() {
1763+
$injector.get('$location');
1764+
}).not.toThrowMinErr('$location', 'nobase',
1765+
"$location in HTML5 mode requires a <base> tag to be present!");
1766+
});
1767+
});
16641768
});
16651769

16661770

0 commit comments

Comments
 (0)