diff options
Diffstat (limited to 'docs/content/guide/$location.ngdoc')
| -rw-r--r-- | docs/content/guide/$location.ngdoc | 662 |
1 files changed, 662 insertions, 0 deletions
diff --git a/docs/content/guide/$location.ngdoc b/docs/content/guide/$location.ngdoc new file mode 100644 index 00000000..a025aa47 --- /dev/null +++ b/docs/content/guide/$location.ngdoc @@ -0,0 +1,662 @@ +@ngdoc overview +@name Using $location +@description + +# What does it do? + +The `$location` service parses the URL in the browser address bar (based on the [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL available to +your application. Changes to the URL in the address bar are reflected into $location service and +changes to $location are reflected into the browser address bar. + +**The $location service:** + +- Exposes the current URL in the browser address bar, so you can + - Watch and observe the URL. + - Change the URL. +- Maintains synchronization between itself and the browser's URL when the user + - Changes the address in the browser's address bar. + - Clicks the back or forward button in the browser (or clicks a History link). + - Clicks on a link in the page. +- Represents the URL object as a set of methods (protocol, host, port, path, search, hash). + + +## Comparing $location to window.location + +<table class="table"> +<thead> + + <tr> + <th class="empty-corner-lt"></th> + <th>window.location</th> + <th>$location service</th> + </tr> + +</thead> +<tbody> + + <tr> + <td class="head">purpose</td> + <td>allow read/write access to the current browser location</td> + <td>same</td> + </tr> + + <tr> + <td class="head">API</td> + <td>exposes "raw" object with properties that can be directly modified</td> + <td>exposes jQuery-style getters and setters</td> + </tr> + + <tr> + <td class="head">integration with angular application life-cycle</td> + <td>none</td> + <td>knows about all internal life-cycle phases, integrates with $watch, ...</td> + </tr> + + <tr> + <td class="head">seamless integration with HTML5 API</td> + <td>no</td> + <td>yes (with a fallback for legacy browsers)</td> + </tr> + + <tr> + <td class="head">aware of docroot/context from which the application is loaded</td> + <td>no - window.location.path returns "/docroot/actual/path"</td> + <td>yes - $location.path() returns "/actual/path"</td> + </tr> + +</tbody> +</table> + +## 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 +{@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` + +- **hashPrefix(prefix)**: {string}<br /> + prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br /> + default: `""` + +### Example configuration +```js +$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: +```js +// 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: + +```js +$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: + +```js + $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 {@link ng.$rootScope.Scope 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](http://www.ietf.org/rfc/rfc3986.txt). When you access the methods: + +- All values that are passed to `$location` setter methods, `path()`, `search()`, `hash()`, are +encoded. +- Getters (calls to methods without parameters) return decoded values for the following methods +`path()`, `search()`, `hash()`. +- When you call the `absUrl()` method, the returned value is a full url with its segments encoded. +- When you call the `url()` method, the returned value is path, search and hash, in the form +`/path?search=a&b=c#hash`. The segments are encoded as well. + + +# 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](http://www.w3.org/TR/html5/history.html). 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. + +<img src="img/guide/hashbang_vs_regular_url.jpg"> + +<table class="table"> +<thead> + + <tr> + <th class="empty-corner-lt"></th> + <th>Hashbang mode</th> + <th>HTML5 mode</th> + </tr> + +</thead> +<tbody> + + <tr> + <td class="head">configuration</td> + <td>the default</td> + <td>{ html5Mode: true }</td> + </tr> + + <tr> + <td class="head">URL format</td> + <td>hashbang URLs in all browsers</td> + <td>regular URLs in modern browser, hashbang URLs in old browser</td> + </tr> + + <tr> + <td class="head"><a href=""> link rewriting</td> + <td>no</td> + <td>yes</td> + </tr> + + <tr> + <td class="head">requires server-side configuration</td> + <td>no</td> + <td>yes</td> + </tr> +</tbody> +</table> + +## Hashbang mode (default mode) + +In this mode, `$location` uses Hashbang URLs in all browsers. + +### Example + +```js +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' + } +)); +``` + +### Crawling your app + +To allow indexing of your AJAX application, you have to add special meta tag in the head section of +your document: + +```html +<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](http://code.google.com/web/ajaxcrawling/docs/specification.html). + +## HTML5 mode + +In HTML5 mode, the `$location` service getters and setters interact with the browser URL address +through the HTML5 history API, which 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. + +- Opening a regular URL in a legacy browser -> redirects to a hashbang URL +- Opening hashbang URL in a modern browser -> rewrites to a regular URL + +### Example + +```js +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 Hasbang +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. + +### Html link rewriting + +When you use HTML5 history API mode, you will need different links in different browsers, but 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 a legacy browser, the URL changes to `/index.html#!/some?foo=bar` +- In a modern browser, the URL changes to `/some?foo=bar` + + +In cases like the following, links are not rewritten; instead, the browser will perform a full page +reload to the original link. + +- Links that contain `target` element<br> + Example: `<a href="/ext/link?a=b" target="_self">link</a>` +- Absolute links that go to a different domain<br> + Example: `<a href="http://angularjs.org/">link</a>` +- Links starting with '/' that lead to a different base path when base is defined<br> + Example: `<a href="/not-my-base/link">link</a>` + +When running Angular in the root of a domain, along side perhaps a normal application in the same +directory, the "otherwise" route handler will try to handle all the URLs, including ones that map +to static files. + +To prevent this, you can set your base href for the app to `<base href=".">` and then prefix links +to URLs that should be handled with `.`. Now, links to locations, which are not to be routed by Angular, +are not prefixed with `.` and will not be intercepted by the `otherwise` rule in your `$routeProvider`. + + +### 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) + +### Crawling your app + +If you want your AJAX application to be indexed by web crawlers, you will need to add the following +meta tag to the HEAD section of your document: + +```html +<meta name="fragment" content="!" /> +``` + +This statement causes a crawler to request links with an empty `_escaped_fragment_` parameter so that +your server can recognize the crawler and serve it HTML snapshots. For more information about this +technique, see [Making AJAX +Applications Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html). + +### Relative links + +Be sure to check all relative links, images, scripts etc. You must either specify the url base in +the head of your main html file (`<base href="/my-base">`) or you must use absolute urls +(starting with `/`) everywhere because relative urls will be resolved to absolute urls using the +initial absolute url of the document, which is often different from the root of the application. + +Running Angular apps with the History API enabled from document root is strongly encouraged as it +takes care of all relative link issues. + +### Sending links among different browsers + +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: + +- Modern browser will rewrite hashbang URLs to regular URLs. +- Older browsers will redirect regular URLs to hashbang URLs. + +### Example + +Here you can see two `$location` instances, both in **Html5 mode**, but on different browsers, so +that you can see the differences. These `$location` services are connected to a fake browsers. Each +input represents address bar of the browser. + +Note that when you type hashbang url into 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 this examples we use `<base href="/base/index.html" />` +<example> + <file name="index.html"> + <div id="html5-mode" ng-controller="Html5Cntl"> + <h3>Browser with History API</h3> + <div ng-address-bar browser="html5"></div><br><br> + $location.protocol() = {{$location.protocol()}}<br> + $location.host() = {{$location.host()}}<br> + $location.port() = {{$location.port()}}<br> + $location.path() = {{$location.path()}}<br> + $location.search() = {{$location.search()}}<br> + $location.hash() = {{$location.hash()}}<br> + <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 id="hashbang-mode" ng-controller="HashbangCntl"> + <h3>Browser without History API</h3> + <div ng-address-bar browser="hashbang"></div><br><br> + $location.protocol() = {{$location.protocol()}}<br> + $location.host() = {{$location.host()}}<br> + $location.port() = {{$location.port()}}<br> + $location.path() = {{$location.path()}}<br> + $location.search() = {{$location.search()}}<br> + $location.hash() = {{$location.hash()}}<br> + <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> + </file> + + <file name="script.js"> + function FakeBrowser(initUrl, baseHref) { + this.onUrlChange = function(fn) { + this.urlChange = fn; + }; + + this.url = function() { + return initUrl; + }; + + this.defer = function(fn, delay) { + setTimeout(function() { fn(); }, delay || 0); + }; + + this.baseHref = function() { + return baseHref; + }; + + this.notifyWhenOutstandingRequests = angular.noop; + } + + var browsers = { + html5: new FakeBrowser('http://www.example.com/base/path?a=b#h', '/base/index.html'), + hashbang: new FakeBrowser('http://www.example.com/base/index.html#!/path?a=b#h', '/base/index.html') + }; + + function Html5Cntl($scope, $location) { + $scope.$location = $location; + } + + function HashbangCntl($scope, $location) { + $scope.$location = $location; + } + + function initEnv(name) { + var root = angular.element(document.getElementById(name + '-mode')); + // We must kill a link to the injector for this element otherwise angular will + // complain that it has been bootstrapped already. + root.data('$injector', null); + angular.bootstrap(root, [function($compileProvider, $locationProvider, $provide){ + $locationProvider.html5Mode(true).hashPrefix('!'); + + $provide.value('$browser', browsers[name]); + $provide.value('$sniffer', {history: name == 'html5'}); + + $compileProvider.directive('ngAddressBar', function() { + return function(scope, elm, attrs) { + var browser = browsers[attrs.browser], + input = angular.element('<input type="text" style="width: 400px">').val(browser.url()), + delay; + + input.on('keypress keyup keydown', function() { + if (!delay) { + delay = setTimeout(fireUrlChange, 250); + } + }); + + browser.url = function(url) { + return input.val(url); + }; + + elm.append('Address: ').append(input); + + function fireUrlChange() { + delay = null; + browser.urlChange(input.val()); + } + }; + }); + }]); + root.on('click', function(e) { + e.stopPropagation(); + }); + } + + initEnv('html5'); + initEnv('hashbang'); + </file> +</example> + + +# 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, {@link ng.$window $window.location.href}. + +## Using $location outside of the scope life-cycle + +`$location` knows about Angular's {@link ng.$rootScope.Scope 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. + + +# Testing with the $location service + +When using `$location` service during testing, you are outside of the angular's {@link +ng.$rootScope.Scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`. + +```js +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 + +<table class="table"> + <thead> + <tr class="head"> + <th>Navigation inside the app</th> + <th>Change to</th> + </tr> + </thead> + + <tbody> + <tr> + <td>$location.href = value<br />$location.hash = value<br />$location.update(value)<br +/>$location.updateHash(value)</td> + <td>$location.path(path).search(search)</td> + </tr> + + <tr> + <td>$location.hashPath = path</td> + <td>$location.path(path)</td> + </tr> + + <tr> + <td>$location.hashSearch = search</td> + <td>$location.search(search)</td> + </tr> + + <tr class="head"> + <td>Navigation outside the app</td> + <td>Use lower level API</td> + </tr> + + <tr> + <td>$location.href = value<br />$location.update(value)</td> + <td>$window.location.href = value</td> + </tr> + + <tr> + <td>$location[protocol | host | port | path | search]</td> + <td>$window.location[protocol | host | port | path | search]</td> + </tr> + + <tr class="head"> + <td>Read access</td> + <td>Change to</td> + </tr> + + <tr> + <td>$location.hashPath</td> + <td>$location.path()</td> + </tr> + + <tr> + <td>$location.hashSearch</td> + <td>$location.search()</td> + </tr> + + <tr> + <td>$location.href<br />$location.protocol<br />$location.host<br />$location.port<br +/>$location.hash</td> + <td>$location.absUrl()<br />$location.protocol()<br />$location.host()<br />$location.port()<br +/>$location.path() + $location.search()</td> + </tr> + + <tr> + <td>$location.path<br />$location.search</td> + <td>$window.location.path<br />$window.location.search</td> + </tr> + </tbody> +</table> + +## Two-way binding to $location + +The Angular's compiler currently does not support two-way binding for methods (see [issue](https://github.com/angular/angular.js/issues/404)). If you should require two-way binding +to the $location object (using {@link input[text] ngModel} directive on an input +field), you will need to specify an extra model property (e.g. `locationPath`) with two watchers +which push $location updates in both directions. For example: +<example> +<file name="index.html"> +<div ng-controller="LocationController"> + <input type="text" ng-model="locationPath" /> +</div> +</file> +<file name="script.js"> +function LocationController($scope, $location) { + $scope.$watch('locationPath', function(path) { + $location.path(path); + }); + $scope.$watch(function() { + return $location.path(); + }, function(path) { + $scope.locationPath = path; + }); +} +</file> +</example> + +# Related API + +* {@link ng.$location $location API} + + + |
