diff --git a/docs/content/error/$location/nobase.ngdoc b/docs/content/error/$location/nobase.ngdoc
index 4dc584c53d6c..11781c167c61 100644
--- a/docs/content/error/$location/nobase.ngdoc
+++ b/docs/content/error/$location/nobase.ngdoc
@@ -4,7 +4,19 @@
@description
If you configure {@link ng.$location `$location`} to use
-{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag.
+{@link api/ng.provider.$locationProvider `html5Mode`} (`history.pushState`), you need to specify the base URL for the application with a [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag or configure
+`$locationProvider` to not require a base tag by passing a definition object with
+`requireBase:false` to `$locationProvider.html5Mode()`:
+
+```javascript
+$locationProvider.html5Mode({
+ enabled: true,
+ requireBase: false
+});
+```
+
+Note that removing the requirement for a tag will have adverse side effects when resolving
+relative paths with `$location` in IE9.
The base URL is then used to resolve all relative URLs throughout the application regardless of the
entry point into the app.
diff --git a/docs/content/guide/$location.ngdoc b/docs/content/guide/$location.ngdoc
index b85245abcab6..e294e25fd0cc 100644
--- a/docs/content/guide/$location.ngdoc
+++ b/docs/content/guide/$location.ngdoc
@@ -91,10 +91,11 @@ To configure the `$location` service, retrieve the
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:
-- **html5Mode(mode)**: {boolean}
- `true` - see HTML5 mode
- `false` - see Hashbang mode
- default: `false`
+- **html5Mode(mode)**: {boolean|Object}
+ `true` or `enabled:true` - see HTML5 mode
+ `false` or `enabled:false` - see Hashbang mode
+ `requireBase:true` - see Relative links
+ default: `enabled:false`
- **hashPrefix(prefix)**: {string}
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)
@@ -328,9 +329,11 @@ reload to the original link.
### Relative links
-Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url base in
-the head of your main html file (``). With that, relative urls will
-always be resolved to this base url, event if the initial url of the document was different.
+Be sure to check all relative links, images, scripts etc. Angular requires you to specify the url
+base in the head of your main html file (``) unless `html5Mode.requireBase` is
+set to `false` in the html5Mode definition object passed to `$locationProvider.html5Mode()`. With
+that, relative urls will always be resolved to this base url, event if the initial url of the
+document was different.
There is one exception: Links that only contain a hash fragment (e.g. ``)
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling
diff --git a/src/ng/location.js b/src/ng/location.js
index ab71f7f1d710..1fcbbf13de57 100644
--- a/src/ng/location.js
+++ b/src/ng/location.js
@@ -584,7 +584,10 @@ function locationGetterSetter(property, preprocess) {
*/
function $LocationProvider(){
var hashPrefix = '',
- html5Mode = false;
+ html5Mode = {
+ enabled: false,
+ requireBase: true
+ };
/**
* @ngdoc method
@@ -606,12 +609,30 @@ function $LocationProvider(){
* @ngdoc method
* @name $locationProvider#html5Mode
* @description
- * @param {boolean=} mode Use HTML5 strategy if available.
- * @returns {*} current value if used as getter or itself (chaining) if used as setter
+ * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value.
+ * If object, sets `enabled` and `requireBase` to respective values.
+ * - **enabled** – `{boolean}` – Sets `html5Mode.enabled`. If true, will rely on
+ * `history.pushState` to change urls where supported. Will fall back to hash-prefixed paths
+ * in browsers that do not support `pushState`.
+ * - **requireBase** - `{boolean}` - Sets `html5Mode.requireBase` (default: `true`). When
+ * html5Mode is enabled, specifies whether or not a tag is required to be present. If
+ * `enabled` and `requireBase` are true, and a base tag is not present, an error will be
+ * thrown when `$location` is injected. See the
+ * {@link guide/$location $location guide for more information}
+ *
+ * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter
*/
this.html5Mode = function(mode) {
- if (isDefined(mode)) {
- html5Mode = mode;
+ if (isBoolean(mode)) {
+ html5Mode.enabled = mode;
+ return this;
+ } else if (isObject(mode)) {
+ html5Mode.enabled = isBoolean(mode.enabled) ?
+ mode.enabled :
+ html5Mode.enabled;
+ html5Mode.requireBase = isBoolean(mode.requireBase) ?
+ mode.requireBase :
+ html5Mode.requireBase;
return this;
} else {
return html5Mode;
@@ -653,8 +674,8 @@ function $LocationProvider(){
initialUrl = $browser.url(),
appBase;
- if (html5Mode) {
- if (!baseHref) {
+ if (html5Mode.enabled) {
+ if (!baseHref && html5Mode.requireBase) {
throw $locationMinErr('nobase',
"$location in HTML5 mode requires a tag to be present!");
}
diff --git a/test/ng/locationSpec.js b/test/ng/locationSpec.js
index f987cd84c852..6e43da0c1dd5 100644
--- a/test/ng/locationSpec.js
+++ b/test/ng/locationSpec.js
@@ -1639,6 +1639,77 @@ describe('$location', function() {
return undefined;
}
+
+ describe('html5Mode', function() {
+ it('should set enabled and requireBase when called with object', function() {
+ module(function($locationProvider) {
+ expect($locationProvider.html5Mode()).toEqual({
+ enabled: false,
+ requireBase: true
+ });
+ });
+
+ inject(function(){});
+ });
+
+
+ it('should only overwrite existing properties if values are boolean', function() {
+ module(function($locationProvider) {
+ $locationProvider.html5Mode({
+ enabled: 'duh',
+ requireBase: 'probably'
+ });
+
+ expect($locationProvider.html5Mode()).toEqual({
+ enabled: false,
+ requireBase: true
+ });
+ });
+
+ inject(function(){});
+ });
+
+
+ it('should not set unknown input properties to html5Mode object', function() {
+ module(function($locationProvider) {
+ $locationProvider.html5Mode({
+ someProp: 'foo'
+ });
+
+ expect($locationProvider.html5Mode()).toEqual({
+ enabled: false,
+ requireBase: true
+ });
+ });
+
+ inject(function(){});
+ });
+
+
+ it('should default to enabled:false and requireBase:true', function() {
+ module(function($locationProvider) {
+ expect($locationProvider.html5Mode()).toEqual({
+ enabled: false,
+ requireBase: true
+ });
+ });
+
+ inject(function(){});
+ });
+
+
+ it('should return html5Mode object when called without value', function() {
+ module(function($locationProvider) {
+ expect($locationProvider.html5Mode()).toEqual({
+ enabled: false,
+ requireBase: true
+ });
+ });
+
+ inject(function(){});
+ });
+ });
+
describe('LocationHtml5Url', function() {
var location, locationIndex;
@@ -1661,6 +1732,39 @@ describe('$location', function() {
// Note: relies on the previous state!
expect(parseLinkAndReturn(location, 'someIgnoredAbsoluteHref', '#test')).toEqual('http://server/pre/otherPath#test');
});
+
+
+ it('should complain if no base tag present', function() {
+ module(function($locationProvider) {
+ $locationProvider.html5Mode(true);
+ });
+
+ inject(function($browser, $injector) {
+ $browser.$$baseHref = undefined;
+ expect(function() {
+ $injector.get('$location');
+ }).toThrowMinErr('$location', 'nobase',
+ "$location in HTML5 mode requires a tag to be present!");
+ });
+ });
+
+
+ it('should not complain if baseOptOut set to true in html5Mode', function() {
+ module(function($locationProvider) {
+ $locationProvider.html5Mode({
+ enabled: true,
+ requireBase: false
+ });
+ });
+
+ inject(function($browser, $injector) {
+ $browser.$$baseHref = undefined;
+ expect(function() {
+ $injector.get('$location');
+ }).not.toThrowMinErr('$location', 'nobase',
+ "$location in HTML5 mode requires a tag to be present!");
+ });
+ });
});