diff options
| author | Brian Ford | 2013-11-06 08:26:45 -0800 | 
|---|---|---|
| committer | Brian Ford | 2013-11-06 08:47:06 -0800 | 
| commit | 5614fd283a22d130cf5e09dade67be3f3c3a97c2 (patch) | |
| tree | 3edbb5131c117e129527129143d5c1c943ca196a | |
| parent | da344daa4023556f8abbef6d8ad87a16362b5861 (diff) | |
| download | angular.js-5614fd283a22d130cf5e09dade67be3f3c3a97c2.tar.bz2 | |
docs(guide/providers): add providers documentation
| -rw-r--r-- | docs/components/angular-bootstrap/bootstrap.js | 4 | ||||
| -rw-r--r-- | docs/content/guide/providers.ngdoc | 425 | ||||
| -rw-r--r-- | docs/src/ngdoc.js | 1 | ||||
| -rw-r--r-- | docs/src/templates/css/docs.css | 8 | 
4 files changed, 437 insertions, 1 deletions
| diff --git a/docs/components/angular-bootstrap/bootstrap.js b/docs/components/angular-bootstrap/bootstrap.js index 81a599e9..f610bfb4 100644 --- a/docs/components/angular-bootstrap/bootstrap.js +++ b/docs/components/angular-bootstrap/bootstrap.js @@ -193,7 +193,9 @@ directive.table = function() {    return {      restrict: 'E',      link: function(scope, element, attrs) { -      element.addClass('table table-bordered table-striped code-table'); +      if (!attrs.class) { +        element.addClass('table table-bordered table-striped code-table'); +      }      }    };  }; diff --git a/docs/content/guide/providers.ngdoc b/docs/content/guide/providers.ngdoc new file mode 100644 index 00000000..adf13bd4 --- /dev/null +++ b/docs/content/guide/providers.ngdoc @@ -0,0 +1,425 @@ +@ngdoc overview +@name Developer Guide: Providers +@description + +# Providers + +Each web application you build is composed of objects that collaborate to get stuff done. These +objects need to be instantiated and wired together for the app to work. In Angular apps most of +these objects are instantiated and wired together automatically by the {@link api/AUTO.$injector +injector service}. + +The injector creates two types of objects, **services** and **specialized objects**. + +Services are objects whose API is defined by the developer writing the service. + +Specialized objects conform to a specific Angular framework API. These objects are one of +controllers, directives, filters or animations. + +The injector needs to know how to create these objects. You tell it by registering a "recipe" for +creating your object with the injector. There are five recipe types. + +The most verbose, but also the most comprehensive one is a Provider recipe. The remaining four +recipe types — Value, Factory, Service and Constant — are just syntactic sugar on top of a provider +recipe. + +Let's take a look at the different scenarios for creating and using services via various recipe +types. We'll start with the simplest case possible where various places in your code need a shared +string and we'll accomplish this via Value recipe. + + +## Note:  A Word on Modules + +In order for the injector to know how to create and wire together all of these objects, it needs +a registry of "recipes". Each recipe has an identifier of the object and the description of how to +create this object. + +Each recipe belongs to an {@link api/angular.Module Angular module}. An Angular module is a bag +that holds one or more recipes. And since manually keeping track of module dependencies is no fun, +a module can contain information about dependencies on other modules as well. + +When an Angular application starts with a given application module, Angular creates a new instance +of injector, which in turn creates a registry of recipes as a union of all recipes defined in the +core "ng" module, application module and its dependencies. The injector then consults the recipe +registry when it needs to create an object for your application. + + +## Value Recipe + +Let's say that we want to have a very simple service called "clientId" that provides a string +representing an authentication id used for some remote API. You would define it like this: + +```javascript +var myApp = angular.module('myApp', []); +myApp.value('clientId', 'a12345654321x'); +``` + +Notice how we created an Angular module called `myApp`, and specified that this module definition +contains a "recipe" for constructing the `clientId` service, which is a simple string in this case. + +And this is how you would display it via Angular's data-binding: + + +```javascript +myApp.controller('DemoController', ['clientId', function DemoController(clientId) { +  this.clientId = clientId; +}]); +``` + +```html +<html ng-app="myApp"> +  <body ng-controller="DemoController as demo"> +    Client ID: {{demo.clientId}} +  </body> +</html> +``` + +In this example, we've used the Value recipe to define the value to provide when `DemoController` +asks for the service with id "clientId". + +On to more complex examples! + + +## Factory Recipe + +The Value recipe is very simple to write, but lacks some important features we often need when +creating services.  Let's now look at the Value recipe's more powerful sibling, the Factory.The +Factory recipe adds the following abilities: + +* ability to use other services (have dependencies) +* service initialization +* delayed/lazy initialization + +The Factory recipe constructs a new service using a function with zero or more arguments (these +are dependencies on other services). The return value of this function is the service instance +created by this recipe. + +Note: All services in Angular are singletons. That means that the injector uses each recipe at most +once to create the object. The injector then caches the reference for all future needs. + +Since Factory is more powerful version of Value recipe, you can construct the same service with it. +Using our previous `clientId` Value recipe example, we can rewrite it as a Factory recipe like +this: + +```javascript +myApp.factory('clientId', function clientIdFactory() { +  return 'a12345654321x'; +}); +``` + +But given that the token is just a string literal, sticking with the Value recipe is still more +appropriate as it makes the code easier to follow. + +Let's say, however, that we would also like create a service that computes a token used for +authentication against a remote API. This token will be called 'apiToken' and will be computed +based on the `clientId` value and a secret stored in browser's local storage: + +```javascript +myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) { +  var encrypt = function(data1, data2) { +    // NSA-proof encryption algorithm: +    return (data1 + ':' + data2).toUpperCase(); +  }; + +  var secret = window.localStorage.getItem('myApp.secret'); +  var apiToken = encrypt(clientId, secret); + +  return apiToken; +}]); +``` + +In the code above, we see how the `apiToken` service is defined via the Factory recipe that depends +on `clientId` service. The factory service then uses NSA-proof encryption to produce an authentication +token. + +Note: It is a best practice to name the factory functions as "<serviceId>Factory" +(e.g. apiTokenFactory). While this names are not required, they help when navigating the code base +or looking at stack traces in the debugger. + +Just like with Value recipe, Factory recipe can create a service of any type, whether it be a +primitive, object literal, function, or even an instance of a custom type. + + +## Service Recipe + +JavaScript developers often use custom types to write object-oriented code. Let's explore how we +could launch a unicorn into the space via our `unicornLauncher` service that is an instance of +custom type: + +```javascript +function UnicornLauncher(apiToken) { + +  this.launchedCount = 0; +  this.launch() { +    // make a request to the remote api and include the apiToken +    ... +    this.launchedCount++; +  } +} +``` + +We are now ready to launch unicorns, but notice that UnicornLauncher depends on our `apiToken`. +We can satisfy this dependency on `apiToken` using the Factory recipe: + +```javascript +myApp.factory('unicornLauncher', ["apiToken", function(apiToken) { +  return new UnicornLauncher(apiToken); +}]); +``` + + +This is, however, exactly the use-case that Service recipe is the most suitable for. + +The Service recipe produces a service just like the Value or Factory recipes, but it does so by +*invoking a constructor with the `new` operator*. The constructor can take zero or more arguments, +which represent dependencies needed by the instance of this type. + +Note: Service recipes follow a design pattern called [constructor +injection](http://www.martinfowler.com/articles/injection.html#ConstructorInjectionWithPicocontainer). + +Since we already have a constructor for our UnicornLauncher type, we can replace the Factory recipe +above with a Service recipe like this: + +```javascript +myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]); +``` + +Much simpler! + +Note: Yes, we have called one of our service recipes 'Service'. We regret this and know that we'll +be somehow punished for our mis-deed. It's like we named one of our offspring 'Children'. Boy, +that would mess with the teachers. + + +## Provider Recipe + +There are two more recipe types left to cover. They are both fairly specialized and are used +infrequently. As already mentioned in the intro, the Provider recipe is the core recipe type and +all the other recipe types are just syntactic sugar on top of it. It is the most verbose recipe +with the most abilities, but for most services it's overkill. + +Provider recipe is syntactically defined as a custom type that implements a `$get` method. This +method is a factory function just like the one we use in Factory recipe. In fact, if you define +a Factory recipe, an empty Provider type with the `$get` method set to your factory function is +automatically created under the hood. + +You should use the Provider recipe only when you want to expose an API for application-wide +configuration that must be made before the application starts. This is usually interesting only +for reusable services whose behavior might need to vary slightly between applications. + +Let's say that our `unicornLauncher` service is so awesome that many apps use it. By default the +launcher shoots unicorns into space without any protective shielding. But on some planets the +atmosphere is so thick that we must wrap every unicorn in tinfoil before sending it on its +intergalactic trip, otherwise they would burn while passing through the atmosphere. It would then +be great if we could configure the launcher to use the tinfoil shielding for each launch in apps +that need it. We can make it configurable it like so: + + +```javascript +myApp.provider('unicornLauncher', function UnicornLauncherProvider() { +  var useTinfoilShielding = false; + +  this.useTinfoilShielding = function(value) { +    useTinfoilShielding = !!value; +  }; + +  this.$get = ["apiToken", function unicornLauncherFactory(apiToken) { + +    // let's assume that the UnicornLauncher constructor was also changed to +    // accept and use the useTinfoilShielding argument +    return new UnicornLauncher(apiToken, useTinfoilShielding); +  }]); +}); +``` + +To turn the tinfoil shielding on in our app, we need to create a config function via the module +API and have the UnicornLauncherProvider injected into it: + +```javascript +myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) { +  unicornLauncherProvider.useTinfoilShielding(true); +}]); +``` + +Notice that the unicorn provider is injected into the config function. This injection is done by a +provider injector which is different from the regular instance injector, in that it instantiates +and wires (injects) all provider instances only. + +During application bootstrap, before Angular goes off creating all services, it configures and +instantiates all providers. We call this the configuration phase of the application life-cycle. +During this phase services aren't accessible because they haven't been created yet. + +Once the configuration phase is over, interaction with providers is disallowed and the process of +creating services starts. We call this part of the application life-cycle the run phase. + + +## Constant Recipe + +We've just learned how Angular splits the life-cycle into configuration phase and run phase and how +you can provide configuration to your application via the config function. Since the config +function runs in the configuration phase when no services are available, it doesn't have access +even to simple value objects created via Value recipe. + +Since simple values, like url prefix, don't have dependencies or configuration, it is often handy +to make them available in both the configuration and run phases. This is what the Constant recipe +is for. + +Let's say that our `unicornLauncher` service can stamp a unicorn with the planet name it's being +launched from if this name was provided during the configuration phase. The planet name is +application specific and is used also by various controllers during the runtime of the application. +We can then define the planet name as a constant like this: + +```javascript +myApp.constant('planetName', 'Greasy Giant'); +``` + +We could then configure the unicornLauncherProvider like this: + +```javascript +myApp.config(['unicornLauncherProvider', 'planetName', function(unicornLauncherProvider, planetName) { +  unicornLauncherProvider.useTinfoilShielding(true); +  unicornLauncherProvider.stampText(planetName); +}]); +``` + +And since Constant recipe makes the value also available at runtime just like the Value recipe, we +can also use it in our controller and template: + +```javascript +myApp.controller('DemoController', ["clientId", "planetName", function DemoController(clientId, planetName) { +  this.clientId = clientId; +  this.planetName = planetName; +}]); +``` + +```html +<html ng-app="myApp"> +  <body ng-controller="DemoController as demo"> +   Client ID: {{demo.clientId}} +   <br> +   Planet Name: {{demo.planetName}} +  </body> +</html> +``` + + +## Special Purpose Objects + +Earlier we mentioned that we also have special purpose objects that are different from services. +These objects extend the framework as plugins and therefore must implement interfaces specified by +Angular. These interfaces are Controller, Directive, Filter and Animation. + +The instructions for the injector to create these special objects (with the exception of the +Controller objects) use the Factory recipe behind the scenes. + +Let's take a look at how we would create a very simple component via the directive api that depends +on the `planetName` constant we've just defined and displays the planet name, in our case: +"Planet Name: Greasy Giant". + +Since the directives are registered via Factory recipe, we can use the same syntax as with factories. + +```javascript +myApp.directive('myPlanet', ['planetName', function myPlanetDirectiveFactory(planetName) { +  // directive definition object +  return { +    restrict: 'E', +    scope: {}, +    link: function($scope, $element) { $element.text('Planet: ' + planetName); } +  } +}]); +``` + +We can then use the component like this: + +```html +<html ng-app="myApp"> +  <body> +   <my-planet></my-planet> +  </body> +</html> +``` + +Using Factory recipes you can also define Angular's filters and animations, but the controllers +are a bit special. You create a controller as a custom type that declares its dependencies as +arguments for its constructor function. This constructor is then registered with a module. Let's +take a look at the `DemoController`, created in one of the early examples: + +```javascript +myApp.controller('DemoController', ['clientId', function DemoController(clientId) { +  this.clientId = clientId; +}]); +``` + +The DemoController is instantiated via its constructor every time the app needs an instance of +DemoController (in our simple app it's just once). So unlike services, controllers are not +singletons. The constructor is called with all the requested services, in our case the `clientId` +service. + + +## Conclusion + +To wrap it up, let's summarize the most important points: + +- The injector uses recipes to create two types of objects: services and special purpose objects +- There are five recipe types that define how to create objects: Value, Factory, Service, Provider +  and Constant. +- Factory and Service are the most commonly used recipes. The only difference between them is that +  Service recipe works better for objects of custom type, while Factory can produce JavaScript +  primitives and functions. +- The Provider recipe is the core recipe type and all the other ones are just syntactic sugar on it. +- Provider is the most complex recipe type. You don't need it unless you are building a reusable +  piece of code that needs global configuration. +- All special purpose objects except for Controller are defined via Factory recipes. + +<table class="table table-bordered code-table"> +<thead> +<tr> +  <th>Features / Recipe type</th> +  <th>Factory</th> +  <th>Service</th> +  <th>Value</th> +  <th>Constant</th> +  <th>Provider</th> +</tr> +</thead> +<tbody> +<tr> +  <td>can have dependencies</td> +  <td class="success">yes</td> +  <td class="success">yes</td> +  <td class="error">no</td> +  <td class="error">no</td> +  <td class="success">yes</td> +</tr> +<tr> +  <td>uses type friendly injection</td> +  <td class="error">no</td> +  <td class="success">yes</td> +  <td class="success">yes\*</td> +  <td class="success">yes\*</td> +  <td class="error">no</td> +</tr> +<tr> +  <td>object available in config phase</td> +  <td class="error">no</td> +  <td class="error">no</td> +  <td class="error">no</td> +  <td class="success">yes</td> +  <td class="success">yes\*\*</td> +</tr> +<tr> +  <td>can create functions/primitives</td> +  <td class="success">yes</td> +  <td class="error">no</td> +  <td class="success">yes</td> +  <td class="success">yes</td> +  <td class="success">yes</td> +</tr> +</tbody> +</table> + +\* at the cost of eager initialization by using `new` operator directly + +\*\* the service object is not available during the config phase, but the provider instance is +(see the `unicornLauncherProvider` example above). + diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js index 17ea1fa8..d367be0c 100644 --- a/docs/src/ngdoc.js +++ b/docs/src/ngdoc.js @@ -1189,6 +1189,7 @@ var GUIDE_PRIORITY = [    'dev_guide.templates',    'di', +  'providers',    'module',    'scope',    'expression', diff --git a/docs/src/templates/css/docs.css b/docs/src/templates/css/docs.css index 783f1d21..9a3e75f2 100644 --- a/docs/src/templates/css/docs.css +++ b/docs/src/templates/css/docs.css @@ -531,3 +531,11 @@ pre ol li {    padding-bottom:30px;    border-bottom:1px solid #aaa;  } + +td.success { +  background-color: #dff0d8; +} + +td.error { +  background-color: #f2dede; +} | 
