mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
425 lines
16 KiB
Text
425 lines
16 KiB
Text
@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).
|
|
|