Improve this Doc

What does it do?

The $location service parses the URL in the browser address bar (based on window.location) and makes the URL available to your application. Changes to the URL in the address bar are reflected into the $location service and changes to $location are reflected into the browser address bar.

The $location service:

Comparing $location to window.location

window.location $location service
purpose allow read/write access to the current browser location same
API exposes "raw" object with properties that can be directly modified exposes jQuery-style getters and setters
integration with angular application life-cycle none knows about all internal life-cycle phases, integrates with $watch, ...
seamless integration with HTML5 API no yes (with a fallback for legacy browsers)
aware of docroot/context from which the application is loaded no - window.location.pathname returns "/docroot/actual/path" yes - $location.path() returns "/actual/path"

When should I use $location?

Any time your application needs to react to a change in the current URL or if you want to change the current URL in the browser.

What does it not do?

It does not cause a full page reload when the browser URL is changed. To reload the page after changing the URL, use the lower-level API, $window.location.href.

General overview of the API

The $location service can behave differently, depending on the configuration that was provided to it when it was instantiated. The default configuration is suitable for many applications, for others customizing the configuration can enable new features.

Once the $location service is instantiated, you can interact with it via jQuery-style getter and setter methods that allow you to get or change the current URL in the browser.

$location service configuration

To configure the $location service, retrieve the $locationProvider and set the parameters as follows:

Example configuration

$locationProvider.html5Mode(true).hashPrefix('!');

Getter and setter methods

$location service provides getter methods for read-only parts of the URL (absUrl, protocol, host, port) and getter / setter methods for url, path, search, hash:

// get the current path
$location.path();

// change the path
$location.path('/newValue')

All of the setter methods return the same $location object to allow chaining. For example, to change multiple segments in one go, chain setters like this:

$location.path('/newValue').search({key: value});

Replace method

There is a special replace method which can be used to tell the $location service that the next time the $location service is synced with the browser, the last history record should be replaced instead of creating a new one. This is useful when you want to implement redirection, which would otherwise break the back button (navigating back would retrigger the redirection). To change the current URL without creating a new browser history record you can call:

$location.path('/someNewPath');
$location.replace();
// or you can chain these as: $location.path('/someNewPath').replace();

Note that the setters don't update window.location immediately. Instead, the $location service is aware of the scope life-cycle and coalesces multiple $location mutations into one "commit" to the window.location object during the scope $digest phase. Since multiple changes to the $location's state will be pushed to the browser as a single change, it's enough to call the replace() method just once to make the entire "commit" a replace operation rather than an addition to the browser history. Once the browser is updated, the $location service resets the flag set by replace() method and future mutations will create new history records, unless replace() is called again.

Setters and character encoding

You can pass special characters to $location service and it will encode them according to rules specified in RFC 3986. When you access the methods:

Hashbang and HTML5 Modes

$location service has two configuration modes which control the format of the URL in the browser address bar: Hashbang mode (the default) and the HTML5 mode which is based on using the HTML5 History API. Applications use the same API in both modes and the $location service will work with appropriate URL segments and browser APIs to facilitate the browser URL change and history management.

Hashbang mode HTML5 mode
configuration the default { html5Mode: true }
URL format hashbang URLs in all browsers regular URLs in modern browser, hashbang URLs in old browser
<a href=""> link rewriting no yes
requires server-side configuration no yes

Hashbang mode (default mode)

In this mode, $location uses Hashbang URLs in all browsers. Angular also does not intercept and rewrite links in this mode. I.e. links work as expected and also perform full page reloads when other parts of the url than the hash fragment was changed.

Example

it('should show example', inject(
  function($locationProvider) {
    $locationProvider.html5Mode(false);
    $locationProvider.hashPrefix('!');
  },
  function($location) {
    // open http://example.com/base/index.html#!/a
    $location.absUrl() == 'http://example.com/base/index.html#!/a'
    $location.path() == '/a'

    $location.path('/foo')
    $location.absUrl() == 'http://example.com/base/index.html#!/foo'

    $location.search() == {}
    $location.search({a: 'b', c: true});
    $location.absUrl() == 'http://example.com/base/index.html#!/foo?a=b&c'

    $location.path('/new').search('x=y');
    $location.absUrl() == 'http://example.com/base/index.html#!/new?x=y'
  }
));

HTML5 mode

In HTML5 mode, the $location service getters and setters interact with the browser URL address through the HTML5 history API. This allows for use of regular URL path and search segments, instead of their hashbang equivalents. If the HTML5 History API is not supported by a browser, the $location service will fall back to using the hashbang URLs automatically. This frees you from having to worry about whether the browser displaying your app supports the history API or not; the $location service transparently uses the best available option.

Note that in this mode, Angular intercepts all links (subject to the "Html link rewriting" rules below) and updates the url in a way that never performs a full page reload.

Example

it('should show example', inject(
  function($locationProvider) {
    $locationProvider.html5Mode(true);
    $locationProvider.hashPrefix('!');
  },
  function($location) {
    // in browser with HTML5 history support:
    // open http://example.com/#!/a -> rewrite to http://example.com/a
    // (replacing the http://example.com/#!/a history record)
    $location.path() == '/a'

    $location.path('/foo');
    $location.absUrl() == 'http://example.com/foo'

    $location.search() == {}
    $location.search({a: 'b', c: true});
    $location.absUrl() == 'http://example.com/foo?a=b&c'

    $location.path('/new').search('x=y');
    $location.url() == 'new?x=y'
    $location.absUrl() == 'http://example.com/new?x=y'

    // in browser without html5 history support:
    // open http://example.com/new?x=y -> redirect to http://example.com/#!/new?x=y
    // (again replacing the http://example.com/new?x=y history item)
    $location.path() == '/new'
    $location.search() == {x: 'y'}

    $location.path('/foo/bar');
    $location.path() == '/foo/bar'
    $location.url() == '/foo/bar?x=y'
    $location.absUrl() == 'http://example.com/#!/foo/bar?x=y'
  }
));

Fallback for legacy browsers

For browsers that support the HTML5 history API, $location uses the HTML5 history API to write path and search. If the history API is not supported by a browser, $location supplies a Hashbang URL. This frees you from having to worry about whether the browser viewing your app supports the history API or not; the $location service makes this transparent to you.

When you use HTML5 history API mode, you will not need special hashbang links. All you have to do is specify regular URL links, such as: <a href="/some?foo=bar">link</a>

When a user clicks on this link,

In cases like the following, links are not rewritten; instead, the browser will perform a full page reload to the original link.

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, even 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 to anchors on the same page without needing to know on which page the user currently is.

Server side

Using this mode requires URL rewriting on server side, basically you have to rewrite all your links to entry point of your application (e.g. index.html). Requiring a <base> tag is also important for this case, as it allows Angular to differentiate between the part of the url that is the application base and the path that should be handled by the application.

Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in legacy browsers and hashbang links in modern browser:

Example

Here you can see two $location instances that show the difference between Html5 mode and Html5 Fallback mode. Note that to simulate different levels of browser support, the $location instances are connected to a fakeBrowser service, which you don't have to set up in actual projects.

Note that when you type hashbang url into the first browser (or vice versa) it doesn't rewrite / redirect to regular / hashbang url, as this conversion happens only during parsing the initial URL = on page reload.

In these examples we use <base href="/base/index.html" />. The inputs represent the address bar of the browser.

Browser in HTML5 mode

  Edit in Plunker
<div ng-controller="LocationController">
  <div ng-address-bar></div><br><br>
  <div>
    $location.protocol() = <span ng-bind="$location.protocol()"></span> <br>
    $location.host() = <span ng-bind="$location.host()"></span> <br>
    $location.port() = <span ng-bind="$location.port()"></span> <br>
    $location.path() = <span ng-bind="$location.path()"></span> <br>
    $location.search() = <span ng-bind="$location.search()"></span> <br>
    $location.hash() = <span ng-bind="$location.hash()"></span> <br>
  </div>
  <div id="navigation">
    <a href="http://www.example.com/base/first?a=b">/base/first?a=b</a> |
    <a href="http://www.example.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
    <a href="/other-base/another?search">external</a>
  </div>
</div>
angular.module('html5-mode', ['fake-browser', 'address-bar'])

// Configure the fakeBrowser. Do not set these values in actual projects.
.constant('initUrl', 'http://www.example.com/base/path?a=b#h')
.constant('baseHref', '/base/index.html')
.value('$sniffer', { history: true })

.controller("LocationController", function($scope, $location) {
  $scope.$location = {};
  angular.forEach("protocol host port path search hash".split(" "), function(method){
   $scope.$location[method] = function(){
     var result = $location[method].call($location);
     return angular.isObject(result) ? angular.toJson(result) : result;
   };
  });
})

.config(function($locationProvider) {
  $locationProvider.html5Mode(true).hashPrefix('!');
})

.run(function($rootElement) {
  $rootElement.on('click', function(e) { e.stopPropagation(); });
});
angular.module('fake-browser', [])

.config(function($provide) {
 $provide.decorator('$browser', function($delegate, baseHref, initUrl) {

  $delegate.onUrlChange = function(fn) {
     this.urlChange = fn;
   };

  $delegate.url = function() {
     return initUrl;
  };

  $delegate.defer = function(fn, delay) {
     setTimeout(function() { fn(); }, delay || 0);
   };

  $delegate.baseHref = function() {
     return baseHref;
   };

   return $delegate;
 });
});
angular.module('address-bar', [])
.directive('ngAddressBar', function($browser, $timeout) {
   return {
     template: 'Address: <input id="addressBar" type="text" style="width: 400px" >',
     link: function(scope, element, attrs){
       var input = element.children("input"), delay;

       input.on('keypress keyup keydown', function(event) {
               delay = (!delay ? $timeout(fireUrlChange, 250) : null);
               event.stopPropagation();
             })
            .val($browser.url());

       $browser.url = function(url) {
         return url ? input.val(url) : input.val();
       };

       function fireUrlChange() {
         delay = null;
         $browser.urlChange(input.val());
       }
     }
   };
 });
var addressBar = element(by.css("#addressBar")),
    url = 'http://www.example.com/base/path?a=b#h';


it("should show fake browser info on load", function(){
  expect(addressBar.getAttribute('value')).toBe(url);

  expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
  expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
  expect(element(by.binding('$location.port()')).getText()).toBe('80');
  expect(element(by.binding('$location.path()')).getText()).toBe('/path');
  expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
  expect(element(by.binding('$location.hash()')).getText()).toBe('h');

});

it("should change $location accordingly", function(){
  var navigation = element.all(by.css("#navigation a"));

  navigation.get(0).click();

  expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/first?a=b");

  expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
  expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
  expect(element(by.binding('$location.port()')).getText()).toBe('80');
  expect(element(by.binding('$location.path()')).getText()).toBe('/first');
  expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
  expect(element(by.binding('$location.hash()')).getText()).toBe('');


  navigation.get(1).click();

  expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/sec/ond?flag#hash");

  expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
  expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
  expect(element(by.binding('$location.port()')).getText()).toBe('80');
  expect(element(by.binding('$location.path()')).getText()).toBe('/sec/ond');
  expect(element(by.binding('$location.search()')).getText()).toBe('{"flag":true}');
  expect(element(by.binding('$location.hash()')).getText()).toBe('hash');
});

Browser in HTML5 Fallback mode (Hashbang mode)

  Edit in Plunker
<div ng-controller="LocationController">
  <div ng-address-bar></div><br><br>
  <div>
    $location.protocol() = <span ng-bind="$location.protocol()"></span> <br>
    $location.host() = <span ng-bind="$location.host()"></span> <br>
    $location.port() = <span ng-bind="$location.port()"></span> <br>
    $location.path() = <span ng-bind="$location.path()"></span> <br>
    $location.search() = <span ng-bind="$location.search()"></span> <br>
    $location.hash() = <span ng-bind="$location.hash()"></span> <br>
  </div>
  <div id="navigation">
    <a href="http://www.example.com/base/first?a=b">/base/first?a=b</a> |
    <a href="http://www.example.com/base/sec/ond?flag#hash">sec/ond?flag#hash</a> |
    <a href="/other-base/another?search">external</a>
  </div>
</div>
angular.module('hashbang-mode', ['fake-browser', 'address-bar'])

// Configure the fakeBrowser. Do not set these values in actual projects.
.constant('initUrl', 'http://www.example.com/base/index.html#!/path?a=b#h')
.constant('baseHref', '/base/index.html')
.value('$sniffer', { history: false })

.config(function($locationProvider) {
  $locationProvider.html5Mode(true).hashPrefix('!');
})

.controller("LocationController", function($scope, $location) {
  $scope.$location = {};
  angular.forEach("protocol host port path search hash".split(" "), function(method){
    $scope.$location[method] = function(){
      var result = $location[method].call($location);
      return angular.isObject(result) ? angular.toJson(result) : result;
    };
  });
})

.run(function($rootElement) {
  $rootElement.on('click', function(e) {
    e.stopPropagation();
  });
});
angular.module('fake-browser', [])

.config(function($provide) {
 $provide.decorator('$browser', function($delegate, baseHref, initUrl) {

  $delegate.onUrlChange = function(fn) {
     this.urlChange = fn;
   };

  $delegate.url = function() {
     return initUrl;
  };

  $delegate.defer = function(fn, delay) {
     setTimeout(function() { fn(); }, delay || 0);
   };

  $delegate.baseHref = function() {
     return baseHref;
   };

   return $delegate;
 });
});
angular.module('address-bar', [])
.directive('ngAddressBar', function($browser, $timeout) {
   return {
     template: 'Address: <input id="addressBar" type="text" style="width: 400px" >',
     link: function(scope, element, attrs){
       var input = element.children("input"), delay;

       input.on('keypress keyup keydown', function(event) {
               delay = (!delay ? $timeout(fireUrlChange, 250) : null);
               event.stopPropagation();
             })
            .val($browser.url());

       $browser.url = function(url) {
         return url ? input.val(url) : input.val();
       };

       function fireUrlChange() {
         delay = null;
         $browser.urlChange(input.val());
       }
     }
   };
 });
var addressBar = element(by.css("#addressBar")),
     url = 'http://www.example.com/base/index.html#!/path?a=b#h';

it("should show fake browser info on load", function(){
  expect(addressBar.getAttribute('value')).toBe(url);

  expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
  expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
  expect(element(by.binding('$location.port()')).getText()).toBe('80');
  expect(element(by.binding('$location.path()')).getText()).toBe('/path');
  expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
  expect(element(by.binding('$location.hash()')).getText()).toBe('h');

});

it("should change $location accordingly", function(){
  var navigation = element.all(by.css("#navigation a"));

  navigation.get(0).click();

  expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/index.html#!/first?a=b");

  expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
  expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
  expect(element(by.binding('$location.port()')).getText()).toBe('80');
  expect(element(by.binding('$location.path()')).getText()).toBe('/first');
  expect(element(by.binding('$location.search()')).getText()).toBe('{"a":"b"}');
  expect(element(by.binding('$location.hash()')).getText()).toBe('');


  navigation.get(1).click();

  expect(addressBar.getAttribute('value')).toBe("http://www.example.com/base/index.html#!/sec/ond?flag#hash");

  expect(element(by.binding('$location.protocol()')).getText()).toBe('http');
  expect(element(by.binding('$location.host()')).getText()).toBe('www.example.com');
  expect(element(by.binding('$location.port()')).getText()).toBe('80');
  expect(element(by.binding('$location.path()')).getText()).toBe('/sec/ond');
  expect(element(by.binding('$location.search()')).getText()).toBe('{"flag":true}');
  expect(element(by.binding('$location.hash()')).getText()).toBe('hash');

});

Caveats

Page reload navigation

The $location service allows you to change only the URL; it does not allow you to reload the page. When you need to change the URL and reload the page or navigate to a different page, please use a lower level API, $window.location.href.

Using $location outside of the scope life-cycle

$location knows about Angular's scope life-cycle. When a URL changes in the browser it updates the $location and calls $apply so that all $watchers / $observers are notified. When you change the $location inside the $digest phase everything is ok; $location will propagate this change into browser and will notify all the $watchers / $observers. When you want to change the $location from outside Angular (for example, through a DOM Event or during testing) - you must call $apply to propagate the changes.

$location.path() and ! or / prefixes

A path should always begin with forward slash (/); the $location.path() setter will add the forward slash if it is missing.

Note that the ! prefix in the hashbang mode is not part of $location.path(); it is actually hashPrefix.

Crawling your app

To allow indexing of your AJAX application, you have to add special meta tag in the head section of your document:

<meta name="fragment" content="!" />

This will cause crawler bot to request links with _escaped_fragment_ param so that your server can recognize the crawler and serve a HTML snapshots. For more information about this technique, see Making AJAX Applications Crawlable.

Testing with the $location service

When using $location service during testing, you are outside of the angular's scope life-cycle. This means it's your responsibility to call scope.$apply().

describe('serviceUnderTest', function() {
  beforeEach(module(function($provide) {
    $provide.factory('serviceUnderTest', function($location){
      // whatever it does...
    });
  });

  it('should...', inject(function($location, $rootScope, serviceUnderTest) {
    $location.path('/new/path');
    $rootScope.$apply();

    // test whatever the service should do...

  }));
});

Migrating from earlier AngularJS releases

In earlier releases of Angular, $location used hashPath or hashSearch to process path and search methods. With this release, the $location service processes path and search methods and then uses the information it obtains to compose hashbang URLs (such as http://server.com/#!/path?search=a), when necessary.

Changes to your code

Navigation inside the app Change to
$location.href = value
$location.hash = value
$location.update(value)
$location.updateHash(value)
$location.path(path).search(search)
$location.hashPath = path $location.path(path)
$location.hashSearch = search $location.search(search)
Navigation outside the app Use lower level API
$location.href = value
$location.update(value)
$window.location.href = value
$location[protocol | host | port | path | search] $window.location[protocol | host | port | path | search]
Read access Change to
$location.hashPath $location.path()
$location.hashSearch $location.search()
$location.href
$location.protocol
$location.host
$location.port
$location.hash
$location.absUrl()
$location.protocol()
$location.host()
$location.port()
$location.path() + $location.search()
$location.path
$location.search
$window.location.path
$window.location.search

Two-way binding to $location

Because $location uses getters/setters, you can use ng-model-options="{ getterSetter: true }" to bind it to ngModel:

  Edit in Plunker
<div ng-controller="LocationController">
  <input type="text" ng-model="locationPath" ng-model-options="{ getterSetter: true }" />
</div>
angular.module('locationExample', [])
.controller('LocationController', ['$scope', '$location', function($scope, $location) {
  $scope.locationPath = function (newLocation) {
    return $location.path(newLocation);
  };
}]);

Related API