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/services.ngdoc | 297 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 docs/content/guide/services.ngdoc (limited to 'docs/content/guide/services.ngdoc') 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