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!"); + }); + }); });