forked from angular/angular.js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathproviders.ngdoc
425 lines (323 loc) · 15.9 KB
/
providers.ngdoc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
@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 to 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 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).