docs(guide/services): rewrite services documentation

This commit is contained in:
Brian Ford
2014-03-03 12:30:33 -08:00
parent 8d6eed21d2
commit 220e7bf2d4
8 changed files with 299 additions and 486 deletions

View File

@@ -1,5 +1,5 @@
@ngdoc overview
@name Angular Services: Using $location
@name Using $location
@description
# What does it do?
@@ -329,7 +329,7 @@ reload to the original link.
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.

View File

@@ -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}

View File

@@ -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'];
```
<example module="MyServiceModule">
<file name="index.html">
<div id="simple" ng-controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p>
</div>
</file>
<file name="script.js">
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'];
</file>
<file name="protractor.js" type="protractor">
it('should test service', function() {
expect(element(by.id('simple')).element(by.model('message')).getAttribute('value'))
.toEqual('test');
});
</file>
</example>
## 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:
<example module="MyServiceModuleDI">
<file name="index.html">
<div id="implicit" ng-controller="myController">
<p>Let's try the notify service, that is implicitly injected into the controller...</p>
<input ng-init="message='test'" ng-model="message">
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p>
</div>
</file>
<file name="script.js">
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);
};
}
</file>
</example>
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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}

View File

@@ -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}.
<div class="alert alert-info">
**Note:** Like other core Angular identifiers built-in services always start with `$`
(i.e. `$http`).
</div>
## 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.
<example module="myServiceModule">
<file name="index.html">
<div id="simple" ng-controller="MyController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng-init="message='test'" ng-model="message" >
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p>
</div>
</file>
<file name="script.js">
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 = [];
}
};
}]);
</file>
<file name="protractor.js" type="protractor">
it('should test service', function() {
expect(element(by.id('simple')).element(by.model('message')).getAttribute('value'))
.toEqual('test');
});
</file>
</example>
<div class="alert alert-info">
**Note:** Angular uses
[**constructor injection**](http://misko.hevery.com/2009/02/19/constructor-injection-vs-setter-injection/).
</div>
### 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);
```
<div class="alert alert-success">
**Best Practice:** Use the array annotation shown above.
</div>
### 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:
<example module="myServiceModuleDI">
<file name="index.html">
<div id="implicit" ng-controller="MyController">
<p>Let's try the notify service, that is implicitly injected into the controller...</p>
<input ng-init="message='test'" ng-model="message">
<button ng-click="callNotify(message);">NOTIFY</button>
<p>(you have to click 3 times to see an alert)</p>
</div>
</file>
<file name="script.js">
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);
};
});
</file>
</example>
<div class="alert alert-error">
**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.
</div>
## 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}