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

Commit

Permalink
feat($location): add ability to opt-out of <base/> tag requirement in…
Browse files Browse the repository at this point in the history
… 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
  • Loading branch information
jeffbcross committed Sep 23, 2014
1 parent ace40d5 commit dc3de7f
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 15 deletions.
14 changes: 13 additions & 1 deletion docs/content/error/$location/nobase.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -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 [`<base href="">`](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 [`<base href="">`](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 <base> 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.
Expand Down
17 changes: 10 additions & 7 deletions docs/content/guide/$location.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@ To configure the `$location` service, retrieve the
{@link ng.$locationProvider $locationProvider} and set the parameters as follows:


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

- **hashPrefix(prefix)**: {string}<br />
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
Expand Down Expand Up @@ -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 (`<base href="/my-base">`). 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 (`<base href="/my-base">`) 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. `<a href="#target">`)
will only change `$location.hash()` and not modify the url otherwise. This is useful for scrolling
Expand Down
35 changes: 28 additions & 7 deletions src/ng/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,10 @@ function locationGetterSetter(property, preprocess) {
*/
function $LocationProvider(){
var hashPrefix = '',
html5Mode = false;
html5Mode = {
enabled: false,
requireBase: true
};

/**
* @ngdoc method
Expand All @@ -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 <base> 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;
Expand Down Expand Up @@ -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 <base> tag to be present!");
}
Expand Down
104 changes: 104 additions & 0 deletions test/ng/locationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 <base> 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 <base> tag to be present!");
});
});
});


Expand Down

0 comments on commit dc3de7f

Please sign in to comment.