From 220e7bf2d448fd80d98d5c2f3cfac3902433df8f Mon Sep 17 00:00:00 2001 From: Brian Ford Date: Mon, 3 Mar 2014 12:30:33 -0800 Subject: docs(guide/services): rewrite services documentation --- docs/content/guide/$location.ngdoc | 662 +++++++++++++++++++++ .../guide/dev_guide.services.$location.ngdoc | 662 --------------------- .../dev_guide.services.creating_services.ngdoc | 106 ---- .../dev_guide.services.injecting_controllers.ngdoc | 123 ---- .../dev_guide.services.managing_dependencies.ngdoc | 113 ---- docs/content/guide/dev_guide.services.ngdoc | 20 - .../dev_guide.services.testing_services.ngdoc | 62 -- ...dev_guide.services.understanding_services.ngdoc | 60 -- docs/content/guide/services.ngdoc | 297 +++++++++ 9 files changed, 959 insertions(+), 1146 deletions(-) create mode 100644 docs/content/guide/$location.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.$location.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.creating_services.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.injecting_controllers.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.managing_dependencies.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.testing_services.ngdoc delete mode 100644 docs/content/guide/dev_guide.services.understanding_services.ngdoc create mode 100644 docs/content/guide/services.ngdoc (limited to 'docs') 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
window.location$location service
purposeallow read/write access to the current browser locationsame
APIexposes "raw" object with properties that can be directly modifiedexposes jQuery-style getters and setters
integration with angular application life-cyclenoneknows about all internal life-cycle phases, integrates with $watch, ...
seamless integration with HTML5 APInoyes (with a fallback for legacy browsers)
aware of docroot/context from which the application is loadedno - window.location.path 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 +{@link ng.$locationProvider $locationProvider} and set the parameters as follows: + + +- **html5Mode(mode)**: {boolean}
+ `true` - see HTML5 mode
+ `false` - see Hashbang mode
+ default: `false` + +- **hashPrefix(prefix)**: {string}
+ prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)
+ 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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Hashbang modeHTML5 mode
configurationthe default{ html5Mode: true }
URL formathashbang URLs in all browsersregular URLs in modern browser, hashbang URLs in old browser
<a href=""> link rewritingnoyes
requires server-side configurationnoyes
+ +## 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 + +``` + +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: `link` + +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
+ Example: `link` +- Absolute links that go to a different domain
+ Example: `link` +- Links starting with '/' that lead to a different base path when base is defined
+ Example: `link` + +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 `` 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 + +``` + +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 (``) 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 `` + + +
+

Browser with History API

+


+ $location.protocol() = {{$location.protocol()}}
+ $location.host() = {{$location.host()}}
+ $location.port() = {{$location.port()}}
+ $location.path() = {{$location.path()}}
+ $location.search() = {{$location.search()}}
+ $location.hash() = {{$location.hash()}}
+ /base/first?a=b | + sec/ond?flag#hash | + external +
+ +
+

Browser without History API

+


+ $location.protocol() = {{$location.protocol()}}
+ $location.host() = {{$location.host()}}
+ $location.port() = {{$location.port()}}
+ $location.path() = {{$location.path()}}
+ $location.search() = {{$location.search()}}
+ $location.hash() = {{$location.hash()}}
+ /base/first?a=b | + sec/ond?flag#hash | + external +
+
+ + + 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('').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'); + +
+ + +# 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Navigation inside the appChange 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 appUse 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 accessChange 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 + +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: + + +
+ +
+
+ +function LocationController($scope, $location) { + $scope.$watch('locationPath', function(path) { + $location.path(path); + }); + $scope.$watch(function() { + return $location.path(); + }, function(path) { + $scope.locationPath = path; + }); +} + +
+ +# Related API + +* {@link ng.$location $location API} + + + diff --git a/docs/content/guide/dev_guide.services.$location.ngdoc b/docs/content/guide/dev_guide.services.$location.ngdoc deleted file mode 100644 index aae42ed2..00000000 --- a/docs/content/guide/dev_guide.services.$location.ngdoc +++ /dev/null @@ -1,662 +0,0 @@ -@ngdoc overview -@name Angular Services: 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
window.location$location service
purposeallow read/write access to the current browser locationsame
APIexposes "raw" object with properties that can be directly modifiedexposes jQuery-style getters and setters
integration with angular application life-cyclenoneknows about all internal life-cycle phases, integrates with $watch, ...
seamless integration with HTML5 APInoyes (with a fallback for legacy browsers)
aware of docroot/context from which the application is loadedno - window.location.path 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 -{@link ng.$locationProvider $locationProvider} and set the parameters as follows: - - -- **html5Mode(mode)**: {boolean}
- `true` - see HTML5 mode
- `false` - see Hashbang mode
- default: `false` - -- **hashPrefix(prefix)**: {string}
- prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)
- 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Hashbang modeHTML5 mode
configurationthe default{ html5Mode: true }
URL formathashbang URLs in all browsersregular URLs in modern browser, hashbang URLs in old browser
<a href=""> link rewritingnoyes
requires server-side configurationnoyes
- -## 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 - -``` - -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: `link` - -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
- Example: `link` -- Absolute links that go to a different domain
- Example: `link` -- Links starting with '/' that lead to a different base path when base is defined
- Example: `link` - -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 `` 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 - -``` - -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 (``) 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 `` - - -
-

Browser with History API

-


- $location.protocol() = {{$location.protocol()}}
- $location.host() = {{$location.host()}}
- $location.port() = {{$location.port()}}
- $location.path() = {{$location.path()}}
- $location.search() = {{$location.search()}}
- $location.hash() = {{$location.hash()}}
- /base/first?a=b | - sec/ond?flag#hash | - external -
- -
-

Browser without History API

-


- $location.protocol() = {{$location.protocol()}}
- $location.host() = {{$location.host()}}
- $location.port() = {{$location.port()}}
- $location.path() = {{$location.path()}}
- $location.search() = {{$location.search()}}
- $location.hash() = {{$location.hash()}}
- /base/first?a=b | - sec/ond?flag#hash | - external -
-
- - - 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('').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'); - -
- - -# 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Navigation inside the appChange 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 appUse 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 accessChange 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 - -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: - - -
- -
-
- -function LocationController($scope, $location) { - $scope.$watch('locationPath', function(path) { - $location.path(path); - }); - $scope.$watch(function() { - return $location.path(); - }, function(path) { - $scope.locationPath = path; - }); -} - -
- -# Related API - -* {@link ng.$location $location API} - - - diff --git a/docs/content/guide/dev_guide.services.creating_services.ngdoc b/docs/content/guide/dev_guide.services.creating_services.ngdoc deleted file mode 100644 index d2d99157..00000000 --- a/docs/content/guide/dev_guide.services.creating_services.ngdoc +++ /dev/null @@ -1,106 +0,0 @@ -@ngdoc overview -@name Angular Services: Creating Services -@description - -While Angular offers several useful services, for any nontrivial application you'll find it useful -to write your own custom services. To do this you begin by registering a service factory function -with a module either via the {@link angular.module Module#factory api} or directly -via the {@link auto.$provide $provide} api inside of module config function. - -All Angular services participate in {@link di dependency injection (DI)} by registering -themselves with Angular's DI system (injector) under a `name` (id) as well as by declaring -dependencies which need to be provided for the factory function of the registered service. The -ability to swap dependencies for mocks/stubs/dummies in tests allows for services to be highly -testable. - - -# Registering Services - -To register a service, you must have a module that this service will be part of. Afterwards, you -can register the service with the module either via the {@link angular.Module Module api} or -by using the {@link auto.$provide $provide} service in the module configuration -function. The following pseudo-code shows both approaches: - -Using the angular.Module api: - -```js -var myModule = angular.module('myModule', []); -myModule.factory('serviceId', function() { - var shinyNewServiceInstance; - //factory function body that constructs shinyNewServiceInstance - return shinyNewServiceInstance; -}); -``` - -Using the $provide service: - -```js -angular.module('myModule', [], function($provide) { - $provide.factory('serviceId', function() { - var shinyNewServiceInstance; - //factory function body that constructs shinyNewServiceInstance - return shinyNewServiceInstance; - }); -}); -``` - -Note that you are not registering a service instance, but rather a factory function that will -create this instance when called. - - -# Dependencies - -Services can not only be depended upon, but can also have their own dependencies. These can be specified -as arguments of the factory function. {@link di Read more} about dependency injection (DI) -in Angular and the use of array notation and the $inject property to make DI annotation -minification-proof. - -Following is an example of a very simple service. This service depends on the `$window` service -(which is passed as a parameter to the factory function) and is just a function. The service simply -stores all notifications; after the third one, the service displays all of the notifications by -window alert. - -```js -angular.module('myModule', [], function($provide) { - $provide.factory('notify', ['$window', function(win) { - var msgs = []; - return function(msg) { - msgs.push(msg); - if (msgs.length == 3) { - win.alert(msgs.join("\n")); - msgs = []; - } - }; - }]); -}); -``` - - -# Instantiating Angular Services - -All services in Angular are instantiated lazily. This means that a service will be created -only when it is needed for instantiation of a service or an application component that depends on it. -In other words, Angular won't instantiate services unless they are requested directly or -indirectly by the application. - - -# Services as singletons - -Lastly, it is important to realize that all Angular services are application singletons. This means -that there is only one instance of a given service per injector. Since Angular is lethally allergic -to global state, it is possible to create multiple injectors, each with its own instance of a -given service, but that is rarely needed, except in tests where this property is crucially -important. - - - -## Related Topics - -* {@link dev_guide.services.understanding_services Understanding Angular Services} -* {@link dev_guide.services.managing_dependencies Managing Service Dependencies} -* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers } -* {@link dev_guide.services.testing_services Testing Angular Services} - -## Related API - -* {@link ng Angular Service API} diff --git a/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc b/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc deleted file mode 100644 index ac58b4e4..00000000 --- a/docs/content/guide/dev_guide.services.injecting_controllers.ngdoc +++ /dev/null @@ -1,123 +0,0 @@ -@ngdoc overview -@name Angular Services: Injecting Services Into Controllers -@description - -Using services as dependencies for controllers is very similar to using services as dependencies -for another service. - -Since JavaScript is a dynamic language, DI can't figure out which services to inject by static -types (like in static typed languages). Therefore, you can specify the service name by using the -`$inject` property, which is an array containing strings with names of services to be injected. -The name must match the corresponding service ID registered with angular. The order of the service -IDs matters: the order of the services in the array will be used when calling the factory function -with injected parameters. The names of parameters in factory function don't matter, but by -convention they match the service IDs, which has added benefits discussed below. - -```js -function myController($loc, $log) { - this.firstMethod = function() { - // use $location service - $loc.setHash(); - }; - this.secondMethod = function() { - // use $log service - $log.info('...'); - }; -} -// which services to inject ? -myController.$inject = ['$location', '$log']; -``` - - - -
-

Let's try this simple notify service, injected into the controller...

- - -

(you have to click 3 times to see an alert)

-
-
- - - angular. - module('MyServiceModule', []). - factory('notify', ['$window', function(win) { - var msgs = []; - return function(msg) { - msgs.push(msg); - if (msgs.length == 3) { - win.alert(msgs.join("\n")); - msgs = []; - } - }; - }]); - - function myController(scope, notifyService) { - scope.callNotify = function(msg) { - notifyService(msg); - }; - } - - myController.$inject = ['$scope','notify']; - - - - it('should test service', function() { - expect(element(by.id('simple')).element(by.model('message')).getAttribute('value')) - .toEqual('test'); - }); - -
- -## Implicit Dependency Injection - -A new feature of Angular DI allows it to determine the dependency from the name of the parameter. -Let's rewrite the above example to show the use of this implicit dependency injection of -`$window`, `$scope`, and our `notify` service: - - - -
-

Let's try the notify service, that is implicitly injected into the controller...

- - -

(you have to click 3 times to see an alert)

-
-
- - - angular. - module('MyServiceModuleDI', []). - factory('notify', function($window) { - var msgs = []; - return function(msg) { - msgs.push(msg); - if (msgs.length == 3) { - $window.alert(msgs.join("\n")); - msgs = []; - } - }; - }); - - function myController($scope, notify) { - $scope.callNotify = function(msg) { - notify(msg); - }; - } - -
- -However, if you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming)) your -code, your variable names will get renamed in which case you will still need to explicitly specify -dependencies with the `$inject` property. - -## Related Topics - -* {@link dev_guide.services.understanding_services Understanding Angular Services} -* {@link dev_guide.services.creating_services Creating Angular Services} -* {@link dev_guide.services.managing_dependencies Managing Service Dependencies} -* {@link dev_guide.services.testing_services Testing Angular Services} - -## Related APIs - -* {@link ng Angular Service API} diff --git a/docs/content/guide/dev_guide.services.managing_dependencies.ngdoc b/docs/content/guide/dev_guide.services.managing_dependencies.ngdoc deleted file mode 100644 index e9d8be8c..00000000 --- a/docs/content/guide/dev_guide.services.managing_dependencies.ngdoc +++ /dev/null @@ -1,113 +0,0 @@ -@ngdoc overview -@name Angular Services: Managing Service Dependencies -@description - -Angular allows services to declare other services as dependencies needed for construction of their -instances. - -To declare dependencies, you specify them in the factory function signature and annotate the -function with the inject annotations either using by setting the `$inject` property, as an array of -string identifiers or using the array notation. Optionally the `$inject` property declaration can be -dropped (see "Inferring `$inject`" but note that that is currently an experimental feature). - -Using the array notation: - -```js -function myModuleCfgFn($provide) { - $provide.factory('myService', ['dep1', 'dep2', function(dep1, dep2) {}]); -} -``` - - -Using the $inject property: - -```js -function myModuleCfgFn($provide) { - var myServiceFactory = function(dep1, dep2) {}; - myServiceFactory.$inject = ['dep1', 'dep2']; - $provide.factory('myService', myServiceFactory); -} -``` - - -Using DI inference (incompatible with minifiers): - -```js -function myModuleCfgFn($provide) { - $provide.factory('myService', function(dep1, dep2) {}); -} -``` - - -Here is an example of two services, one of which depends on the other and both -of which depend on other services that are provided by the Angular framework: - -```js -/** - * batchLog service allows for messages to be queued in memory and flushed - * to the console.log every 50 seconds. - * - * @param {*} message Message to be logged. - */ - function batchLogModule($provide){ - $provide.factory('batchLog', ['$interval', '$log', function($interval, $log) { - var messageQueue = []; - - function log() { - if (messageQueue.length) { - $log.log('batchLog messages: ', messageQueue); - messageQueue = []; - } - } - - // start periodic checking - $interval(log, 50000); - - return function(message) { - messageQueue.push(message); - } - }]); - - /** - * routeTemplateMonitor monitors each $route change and logs the current - * template via the batchLog service. - */ - $provide.factory('routeTemplateMonitor', - ['$route', 'batchLog', '$rootScope', - function($route, batchLog, $rootScope) { - $rootScope.$on('$routeChangeSuccess', function() { - batchLog($route.current ? $route.current.template : null); - }); - }]); - } - - // get the main service to kick off the application - angular.injector([batchLogModule]).get('routeTemplateMonitor'); -``` - -Things to notice in this example: - -* The `batchLog` service depends on the built-in {@link ng.$interval $interval} and -{@link ng.$log $log} services, and allows messages to be logged into the -`console.log` in batches. -* The `routeTemplateMonitor` service depends on the built-in {@link ngRoute.$route -$route} service as well as our custom `batchLog` service. -* Both of our services use the factory function signature and array notation for inject annotations -to declare their dependencies. It is important that the order of the string identifiers in the array -is the same as the order of argument names in the signature of the factory function. Unless the -dependencies are inferred from the function signature, it is this array with IDs and their order -that the injector uses to determine which services and in which order to inject. - - -## Related Topics - -* {@link dev_guide.services.understanding_services Understanding Angular Services} -* {@link dev_guide.services.creating_services Creating Angular Services} -* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers } -* {@link dev_guide.services.testing_services Testing Angular Services} - - -## Related API - -* {@link ./ng Angular Service API} -* {@link angular.injector Angular Injector API} diff --git a/docs/content/guide/dev_guide.services.ngdoc b/docs/content/guide/dev_guide.services.ngdoc deleted file mode 100644 index 29eba46f..00000000 --- a/docs/content/guide/dev_guide.services.ngdoc +++ /dev/null @@ -1,20 +0,0 @@ -@ngdoc overview -@name Angular Services -@description - -Services are a feature that Angular brings to client-side web apps from the server side, where -services have been commonly used for a long time. Services in Angular apps are substitutable -objects that are wired together using {@link di dependency injection (DI)}. - - -## Related Topics - -* {@link dev_guide.services.understanding_services Understanding Angular Services} -* {@link dev_guide.services.creating_services Creating Angular Services} -* {@link dev_guide.services.managing_dependencies Managing Service Dependencies} -* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers} -* {@link dev_guide.services.testing_services Testing Angular Services} - -## Related API - -* {@link ./ng Angular Service API} diff --git a/docs/content/guide/dev_guide.services.testing_services.ngdoc b/docs/content/guide/dev_guide.services.testing_services.ngdoc deleted file mode 100644 index 67a1635d..00000000 --- a/docs/content/guide/dev_guide.services.testing_services.ngdoc +++ /dev/null @@ -1,62 +0,0 @@ -@ngdoc overview -@name Angular Services: Testing Angular Services -@description - -The following is a unit test for the 'notify' service in the 'Dependencies' example in {@link -dev_guide.services.creating_services Creating Angular Services}. The unit test example uses Jasmine -spy (mock) instead of a real browser alert. - -```js -var mock, notify; - -beforeEach(function() { - mock = {alert: jasmine.createSpy()}; - - module(function($provide) { - $provide.value('$window', mock); - }); - - inject(function($injector) { - notify = $injector.get('notify'); - }); -}); - -it('should not alert first two notifications', function() { - notify('one'); - notify('two'); - - expect(mock.alert).not.toHaveBeenCalled(); -}); - -it('should alert all after third notification', function() { - notify('one'); - notify('two'); - notify('three'); - - expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree"); -}); - -it('should clear messages after alert', function() { - notify('one'); - notify('two'); - notify('third'); - notify('more'); - notify('two'); - notify('third'); - - expect(mock.alert.callCount).toEqual(2); - expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]); -}); -``` - - -## Related Topics - -* {@link dev_guide.services.understanding_services Understanding Angular Services} -* {@link dev_guide.services.creating_services Creating Angular Services} -* {@link dev_guide.services.managing_dependencies Managing Service Dependencies} -* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers} - -## Related API - -* {@link ./ng Angular Service API} diff --git a/docs/content/guide/dev_guide.services.understanding_services.ngdoc b/docs/content/guide/dev_guide.services.understanding_services.ngdoc deleted file mode 100644 index b1750884..00000000 --- a/docs/content/guide/dev_guide.services.understanding_services.ngdoc +++ /dev/null @@ -1,60 +0,0 @@ -@ngdoc overview -@name Angular Services: Understanding Angular Services -@description - -## What are Angular Services? - -Angular services are singletons objects or functions that carry out specific tasks common to web apps. -Angular has a number of built in services, such as the {@link ng.$http $http service}, which -provides access to the browser's `XMLHttpRequest` object for making requests to a server. Like other core -Angular variables and identifiers, the built-in services always start with `$` (such as `$http` mentioned -above). You can also create your own custom services. - -## Using a Service - -To use an Angular service, you identify it as a dependency for the component (controller, service, -filter or directive) that depends on the service. Angular's dependency injection subsystem takes -care of the rest. The Angular injector subsystem is in charge of service instantiation, resolution -of dependencies, and provision of dependencies to components as requested. - -Angular injects dependencies using -["constructor" injection](http://misko.hevery.com/2009/02/19/constructor-injection-vs-setter-injection/). -The dependency is passed to the component's factory/constructor function. Because JavaScript is a dynamically -typed language, Angular's dependency injection subsystem cannot use static types to identify service -dependencies. For this reason a component must, explicitly, define its dependencies by using one of the -{@link di injection annotation} methods. For example, by providing a `$inject` property: - - var MyController = function($location) { ... }; - MyController.$inject = ['$location']; - myModule.controller('MyController', MyController); - -Or by providing an "inline" injection annotation: - - var myService = function($http) { ... }; - myModule.factory('myService', ['$http', myService]); - -## Defining a Service - -Application developers are free to define their own services by registering their name, and **service -factory function**, in Angular modules. - -The purpose of the **service factory function** is to generate the single object, or function, that -represents the service to the rest of the application. That object, or function, will then be -injected into any component (controller, service, filter or directive) that specifies a dependency -on the service. - -Angular factory functions are executed lazily. That is, they are only executed when needed -to satisfy a dependency, and are then executed exactly once for each service. Everything that is -dependent on this service gets a reference to the single instance generated by the service factory. - -## Related Topics - -* {@link guide/di About Angular Dependency Injection} -* {@link guide/dev_guide.services.creating_services Creating Angular Services} -* {@link guide/dev_guide.services.managing_dependencies Managing Service Dependencies} -* {@link guide/dev_guide.services.testing_services Testing Angular Services} - -## Related API - -* {@link ./ng Angular Service API} -* {@link angular.injector Injector API} diff --git a/docs/content/guide/services.ngdoc b/docs/content/guide/services.ngdoc new file mode 100644 index 00000000..d92be142 --- /dev/null +++ b/docs/content/guide/services.ngdoc @@ -0,0 +1,297 @@ +@ngdoc overview +@name Services +@description + +# Services + +Angular services are substitutable objects that are wired together using {@link di dependency +injection (DI)}. You can use services to organize and share code across your app. + +Angular services are: + +* Lazily instantiated – Angular only instantiates a service when an application component depends + on it. +* Singletons – Each component is dependent on a service gets a reference to the single instance + generated by the service factory. + +Angular offers several useful services (like {@link ng.$http `$http`}) but for most applications +you'll also want to {@link services#creating-services create your own}. + +
+**Note:** Like other core Angular identifiers built-in services always start with `$` +(i.e. `$http`). +
+ + +## Using a Service + +To use an Angular service, you add it as a dependency for the component (controller, service, +filter or directive) that depends on the service. Angular's {@link di dependency injection} +subsystem takes care of the rest. + + + +
+

Let's try this simple notify service, injected into the controller...

+ + +

(you have to click 3 times to see an alert)

+
+
+ + + angular. + module('myServiceModule', []). + controller('MyController', ['$scope','notify', function ($scope, notify) { + $scope.callNotify = function(msg) { + notify(msg); + }; + }]). + factory('notify', ['$window', function(win) { + var msgs = []; + return function(msg) { + msgs.push(msg); + if (msgs.length == 3) { + win.alert(msgs.join("\n")); + msgs = []; + } + }; + }]); + + + + it('should test service', function() { + expect(element(by.id('simple')).element(by.model('message')).getAttribute('value')) + .toEqual('test'); + }); + +
+ +
+**Note:** Angular uses +[**constructor injection**](http://misko.hevery.com/2009/02/19/constructor-injection-vs-setter-injection/). +
+ +### Explicit Dependency Injection + +A component should explicitly define its dependencies using one of the {@link di injection +annotation} methods: + +1. Inline array injection annotation (preferred): + ```js + myModule.controller('MyController', ['$location', function($location) { ... }]); + ``` + +2. `$inject` property: + ```js + var MyController = function($location) { ... }; + MyController.$inject = ['$location']; + myModule.controller('MyController', MyController); + ``` + +
+**Best Practice:** Use the array annotation shown above. +
+ +### Implicit Dependency Injection + +Even if you don't annotate your dependencies, Angular's DI can determine the dependency from the +name of the parameter. Let's rewrite the above example to show the use of this implicit dependency +injection of `$window`, `$scope`, and our `notify` service: + + + +
+

Let's try the notify service, that is implicitly injected into the controller...

+ + +

(you have to click 3 times to see an alert)

+
+
+ + + angular.module('myServiceModuleDI', []). + factory('notify', function($window) { + var msgs = []; + return function(msg) { + msgs.push(msg); + if (msgs.length == 3) { + $window.alert(msgs.join("\n")); + msgs = []; + } + }; + }). + controller('MyController', function($scope, notify) { + $scope.callNotify = function(msg) { + notify(msg); + }; + }); + +
+ +
+**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming) your code, +your variable names will get renamed unless you use one of the annotation techniques above. +
+ + +## Creating Services + +Application developers are free to define their own services by registering the service's name and +**service factory function**, with an Angular module. + +The **service factory function** generates the single object or function that represents the +service to the rest of the application. The object or function returned by the service is +injected into any component (controller, service, filter or directive) that specifies a dependency +on the service. + +### Registering Services + +Services are registered to modules via the {@link angular.Module Module API}. +Typically you use the {@link angular.module Module#factory} API to register a service: + +```javascript +var myModule = angular.module('myModule', []); +myModule.factory('serviceId', function() { + var shinyNewServiceInstance; + //factory function body that constructs shinyNewServiceInstance + return shinyNewServiceInstance; +}); +``` + +Note that you are not registering a **service instance**, but rather a **factory function** that +will create this instance when called. + +### Dependencies + +Services can have their own dependencies. Just like declaring dependencies in a controller, you +declare dependencies by specifying them in the service's factory function signature. + +The example module below has two services, each with various dependencies: + +```js +var batchModule = angular.module('batchModule', []); + +/** + * The `batchLog` service allows for messages to be queued in memory and flushed + * to the console.log every 50 seconds. + * + * @param {*} message Message to be logged. + */ +batchModule.factory('batchLog', ['$interval', '$log', function($interval, $log) { + var messageQueue = []; + + function log() { + if (messageQueue.length) { + $log.log('batchLog messages: ', messageQueue); + messageQueue = []; + } + } + + // start periodic checking + $interval(log, 50000); + + return function(message) { + messageQueue.push(message); + } +}]); + +/** + * `routeTemplateMonitor` monitors each `$route` change and logs the current + * template via the `batchLog` service. + */ +batchModule.factory('routeTemplateMonitor', ['$route', 'batchLog', '$rootScope', + function($route, batchLog, $rootScope) { + $rootScope.$on('$routeChangeSuccess', function() { + batchLog($route.current ? $route.current.template : null); + }); + }]); + +``` + +In the example, note that: + +* The `batchLog` service depends on the built-in {@link ng.$interval `$interval`} and + {@link ng.$log `$log`} services. +* The `routeTemplateMonitor` service depends on the built-in {@link ngRoute.$route `$route`} + service and our custom `batchLog` service. +* Both services use the and array notation to declare their dependencies. +* That the order of identifiers in the array is the same as the order of argument + names in the factory function. + +### Registering a Service with `$provide` + +You can also register services via the {@link auto.$provide `$provide`} service inside of a +module's `config` function: + +```javascript +angular.module('myModule', []).config(function($provide) { + $provide.factory('serviceId', function() { + var shinyNewServiceInstance; + //factory function body that constructs shinyNewServiceInstance + return shinyNewServiceInstance; + }); +}); +``` + +This is technique is often used in unit tests to mock out a service's dependencies. + + +## Unit Testing + +The following is a unit test for the `notify` service from the {@link services#creating-services +Creating Angular Services} example above. The unit test example uses a Jasmine spy (mock) instead +of a real browser alert. + +```js +var mock, notify; + +beforeEach(function() { + mock = {alert: jasmine.createSpy()}; + + module(function($provide) { + $provide.value('$window', mock); + }); + + inject(function($injector) { + notify = $injector.get('notify'); + }); +}); + +it('should not alert first two notifications', function() { + notify('one'); + notify('two'); + + expect(mock.alert).not.toHaveBeenCalled(); +}); + +it('should alert all after third notification', function() { + notify('one'); + notify('two'); + notify('three'); + + expect(mock.alert).toHaveBeenCalledWith("one\ntwo\nthree"); +}); + +it('should clear messages after alert', function() { + notify('one'); + notify('two'); + notify('third'); + notify('more'); + notify('two'); + notify('third'); + + expect(mock.alert.callCount).toEqual(2); + expect(mock.alert.mostRecentCall.args).toEqual(["more\ntwo\nthird"]); +}); +``` + + +## Related Topics + +* {@link guide/di Dependency Injection in AngularJS} + +## Related API + +* {@link ./ng Angular Service API} +* {@link angular.injector Injector API} -- cgit v1.2.3