parent
12d4e17f94
commit
ab6d362650
|
@ -7,7 +7,9 @@ import {Logger} from '../logger.service';
|
|||
@Injectable()
|
||||
export class HeroService {
|
||||
|
||||
//#docregion ctor
|
||||
constructor(private _logger: Logger) { }
|
||||
//#enddocregion ctor
|
||||
|
||||
getHeroes() {
|
||||
this._logger.log('Getting heroes ...')
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Examples of provider arrays
|
||||
//#docplaster
|
||||
import { Component, Host, Inject, Injectable,
|
||||
provide, Provider} from 'angular2/core';
|
||||
|
||||
|
@ -103,10 +104,8 @@ class EvenBetterLogger {
|
|||
template: template,
|
||||
providers:
|
||||
//#docregion providers-5
|
||||
[
|
||||
UserService,
|
||||
provide(Logger, {useClass: EvenBetterLogger})
|
||||
]
|
||||
[ UserService,
|
||||
provide(Logger, {useClass: EvenBetterLogger}) ]
|
||||
//#enddocregion providers-5
|
||||
})
|
||||
export class ProviderComponent5 {
|
||||
|
@ -131,11 +130,9 @@ class OldLogger {
|
|||
template: template,
|
||||
providers:
|
||||
//#docregion providers-6a
|
||||
[
|
||||
NewLogger,
|
||||
[ NewLogger,
|
||||
// Not aliased! Creates two instances of `NewLogger`
|
||||
provide(OldLogger, {useClass:NewLogger})
|
||||
]
|
||||
provide(OldLogger, {useClass:NewLogger}) ]
|
||||
//#enddocregion providers-6a
|
||||
})
|
||||
export class ProviderComponent6a {
|
||||
|
@ -156,11 +153,9 @@ export class ProviderComponent6a {
|
|||
template: template,
|
||||
providers:
|
||||
//#docregion providers-6b
|
||||
[
|
||||
NewLogger,
|
||||
[ NewLogger,
|
||||
// Alias OldLogger w/ reference to NewLogger
|
||||
provide(OldLogger, {useExisting: NewLogger})
|
||||
]
|
||||
provide(OldLogger, {useExisting: NewLogger}) ]
|
||||
//#enddocregion providers-6b
|
||||
})
|
||||
export class ProviderComponent6b {
|
||||
|
@ -306,7 +301,9 @@ export class ProviderComponent10b {
|
|||
log: (msg:string)=> this._logger.logs.push(msg),
|
||||
logs: []
|
||||
}
|
||||
// #enddocregion provider-10-logger
|
||||
this._logger.log("Optional logger was not available.")
|
||||
// #docregion provider-10-logger
|
||||
}
|
||||
// #enddocregion provider-10-logger
|
||||
else {
|
||||
|
|
|
@ -399,38 +399,41 @@ include ../../../../_includes/_util-fns
|
|||
We're likely to need the same logger service everywhere in our application
|
||||
so we put it at the root level of the application in the `app/` folder and
|
||||
we register it in the `providers` array of the metadata for our application root component, `AppComponent`.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-1', 'app/app.component.ts (providers)')
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger', 'app/app.component.ts (providers)')
|
||||
:marked
|
||||
If we forget to register it, Angular will throw an exception when it first needs the logger:
|
||||
If we forget to register it, Angular throws an exception when it first looks for the logger:
|
||||
code-example(format).
|
||||
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
||||
:marked
|
||||
That's telling us that the dependency injector couldn't find the *provider* for the logger
|
||||
when it first tried to call the logger — inside the `HeroService` when the `HeroListComponent`
|
||||
was calling for heroes.
|
||||
The *provider* is the subject of our next section.
|
||||
That's Angular telling us that the dependency injector couldn't find the *provider* for the logger.
|
||||
It needed that provider to create a `Logger` to inject into a new `HeroService` which it needed to
|
||||
create and inject into a new `HeroListComponent`.
|
||||
|
||||
The chain of creations started with the `Logger` provider. The *provider* is the subject of our next section.
|
||||
|
||||
But wait! What if the logger is optional?
|
||||
<a id="optional"></a>
|
||||
### Optional dependencies
|
||||
|
||||
Our `HeroService` currently requires a `Logger`. What if we could get by without a logger?
|
||||
We'd use it if we had it, ignore it if we didn't.
|
||||
We'd use it if we had it, ignore it if we didn't. We can do that.
|
||||
|
||||
First we import the `@Optional` decorator.
|
||||
First import the `@Optional()` decorator.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','import-optional')(format='.')
|
||||
:marked
|
||||
Then rewrite the constructor with that decorator to make the logger optional.
|
||||
Then rewrite the constructor with `@Optional()` decorator preceding the private `_logger` parameter.
|
||||
That tells the injector that `_logger` is optional.
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-ctor')(format='.')
|
||||
:marked
|
||||
Be prepared for a null logger. If we don't register one somewhere up the line,
|
||||
the injector will inject `null`. We have a method that logs. What can we do?
|
||||
the injector will inject `null`. We have a method that logs.
|
||||
What can we do to avoid a null reference exception?
|
||||
|
||||
We could substitute a *do-nothing* logger stub so that our methods continue to work:
|
||||
We could substitute a *do-nothing* logger stub so that calling methods continue to work:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-logger')(format='.')
|
||||
:marked
|
||||
Obviously we'd take a more sophisticated approach if the logger were optional
|
||||
elsewhere as well.
|
||||
in multiple locations.
|
||||
|
||||
But enough about optional loggers. In our sample application, the `Logger` is required.
|
||||
We must register a `Logger` with the application injector using *providers*
|
||||
|
@ -445,18 +448,15 @@ code-example(format).
|
|||
The injector relies on **providers** to create instances of the services
|
||||
that it injects into components and other services.
|
||||
|
||||
We must register *providers* with the injector or it won't know what to do.
|
||||
We must register a service *provider* with the injector or it won't know how to create the service.
|
||||
|
||||
Earlier we registered the `Logger` service in the `providers` array of the metadata for the `AppComponent` like this:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger')
|
||||
:marked
|
||||
The `providers` array appears to hold service classes (one service class in this example).
|
||||
In reality it holds instances of the [Provider](../api/core/Provider-class.html) class.
|
||||
|
||||
In our example, when the `HeroService` constructor specifies `logger:Logger`, it's expecting
|
||||
something that has the shape and behavior of the `Logger` class.
|
||||
|
||||
There are many ways to *provide* something that has the shape and behavior of a `Logger`.
|
||||
The `providers` array appears to hold a service class.
|
||||
In reality it holds an instance the [Provider](../api/core/Provider-class.html) class that can create that service.
|
||||
|
||||
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||
The `Logger` class itself is an obvious and natural provider - it has the right shape and it's designed to be created.
|
||||
But it's not the only way.
|
||||
|
||||
|
@ -511,7 +511,7 @@ code-example(format).
|
|||
### Aliased Class Providers
|
||||
|
||||
Suppose there is an old component that depends upon an `OldLogger` class.
|
||||
`OldLogger` has the same interface as the `NewLogger` but, for some reason,
|
||||
`OldLogger` has the same interface as the `NewLogger` but for some reason
|
||||
we can't update the old component to use it.
|
||||
|
||||
When the *old* component logs a message with `OldLogger`,
|
||||
|
@ -545,14 +545,15 @@ code-example(format).
|
|||
|
||||
Sometimes we need to create the dependent value dynamically,
|
||||
based on information we won't have until the last possible moment.
|
||||
Maybe the information can change.
|
||||
Maybe the information keeps changes repeatedly in the course of the browser session..
|
||||
|
||||
Suppose also that the injectable service has no independent access to the source of this information.
|
||||
|
||||
This situation calls for a **factory provider** as we illustrate next.
|
||||
This situation calls for a **factory provider**.
|
||||
|
||||
Our HeroService should hide *secret* heroes from normal users.
|
||||
Only authorized users should see them.
|
||||
Let's illustrate by adding a new business requirement:
|
||||
the HeroService must hide *secret* heroes from normal users.
|
||||
Only authorized users should see secret heroes.
|
||||
|
||||
Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user.
|
||||
It needs to know if the user is authorized to see secret heroes.
|
||||
|
@ -560,6 +561,8 @@ code-example(format).
|
|||
as when we log in a different user.
|
||||
|
||||
Unlike `EvenBetterLogger`, we can't inject the `UserService` into the `HeroService`.
|
||||
The `HeroService` won't have direct access to the user information to decide
|
||||
who is authoriazed and who is not.
|
||||
.l-sub-section
|
||||
:marked
|
||||
Why? We don't know either. Stuff like this happens.
|
||||
|
@ -573,9 +576,9 @@ code-example(format).
|
|||
A factory provider needs a factory function:
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','factory', 'app/heroes/hero.service.provider.ts (factory)')(format='.')
|
||||
:marked
|
||||
Although our `HeroService` knows nothing about the `UserService`, our factory function does.
|
||||
Although the `HeroService` has no access to the `UserService`, our factory function does.
|
||||
|
||||
We inject both the `Logger` and the `UserService` into the factory provider:
|
||||
We inject both the `Logger` and the `UserService` into the factory provider and let the injector pass them along to the factory function:
|
||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','provider', 'app/heroes/hero.service.provider.ts (provider)')(format='.')
|
||||
:marked
|
||||
|
||||
|
@ -586,12 +589,15 @@ code-example(format).
|
|||
|
||||
The `deps` property is an array of [provider tokens](#token).
|
||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||
:marked
|
||||
Notice that we've captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes it easier for us to register our `HeroService` whereever we need it.
|
||||
Notice that we captured the factory provider in an exported variable, `heroServiceProvider`.
|
||||
This extra step makes the factory provider re-usable.
|
||||
We can register our `HeroService` with this variable whereever we need it.
|
||||
|
||||
In our sample, we need it only in the `HeroesComponent`
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` array:
|
||||
where it replaces the previous `HeroService` registration in the metadata `providers` array.
|
||||
Here we see the new and the old implementation side-by-side:
|
||||
+makeTabs(
|
||||
`dependency-injection/ts/app/heroes/heroes.component.ts,
|
||||
dependency-injection/ts/app/heroes/heroes.component.1.ts`,
|
||||
|
@ -606,11 +612,11 @@ code-example(format).
|
|||
|
||||
When we register a provider with an injector we associate that provider with a dependency injection token.
|
||||
The injector maintains an internal *token/provider* map that it references when
|
||||
asked for a dependency
|
||||
asked for a dependency. The token is the key to the map.
|
||||
|
||||
In all previous examples, the dependency value has been a class *instance* and
|
||||
the class *type* served as its own lookup token.
|
||||
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the token.
|
||||
the class *type* served as its own lookup key.
|
||||
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the key/token.
|
||||
+makeExample('dependency-injection/ts/app/injector.component.ts','get-hero-service')(format='.')
|
||||
:marked
|
||||
We have similar good fortune (in typescript) when we write a constructor that requires an injected class-based dependency.
|
||||
|
@ -618,7 +624,7 @@ code-example(format).
|
|||
service associated with that `HeroService` class token:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-8-ctor')(format=".")
|
||||
:marked
|
||||
This is all especially convenient when we consider that most dependency values are provided by classes.
|
||||
This is especially convenient when we consider that most dependency values are provided by classes.
|
||||
|
||||
### Non-class Dependencies
|
||||
|
||||
|
@ -626,7 +632,7 @@ code-example(format).
|
|||
Sometimes the thing we want to inject is a string, a function, or an object.
|
||||
|
||||
Applications often define configuration objects with lots of small facts like the title of the application or the address of a web api endpoint.
|
||||
These configuration objects aren't always instances of a class. They're just objects ... like this one:
|
||||
These configuration objects aren't always instances of a class. They tend to be object hashes like this one:
|
||||
|
||||
+makeExample('dependency-injection/ts/app/app.config.ts','config','app/app-config.ts')(format='.')
|
||||
:marked
|
||||
|
@ -638,13 +644,19 @@ code-example(format).
|
|||
// begin Typescript only
|
||||
<a id="interface"></a>
|
||||
:marked
|
||||
### Interfaces aren't valid tokens
|
||||
|
||||
The `CONFIG` constant has an interface, `Config`. Unfortunately, we
|
||||
**cannot use an interface as a token**
|
||||
cannot use an interface as a token:
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a-interface')(format=".")
|
||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor-interface')(format=".")
|
||||
:marked
|
||||
It's not Angular's fault. An interface is a TypeScript design-time artifact.
|
||||
It disappears from the generated JavaScript so there is no interface type information for Angular to find at runtime.
|
||||
That seems strange if we're used to dependency injection in strongly-typed languages where
|
||||
an interface is the preferred dependency lookup key.
|
||||
|
||||
It's not Angular's fault. An interface is a TypeScript design-time artifact. JavaScript doesn't have interfaces.
|
||||
The TypeScript interface disappears from the generated JavaScript.
|
||||
There is no interface type information left for Angular to find at runtime.
|
||||
// end Typescript only
|
||||
<a id="string-token"></a>
|
||||
:marked
|
||||
|
@ -684,7 +696,9 @@ code-example(format).
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
Angular uses `OpaqueTokens` to register all of its non-class dependencies.
|
||||
Angular itself uses `OpaqueTokens` to register all of its own non-class dependencies. For example,
|
||||
[HTTP_PROVIDERS](https://angular.io/docs/ts/latest/api/http/HTTP_PROVIDERS-let.html)
|
||||
is the `OpaqueToken` associated with an array of providers for persisting data with the Angular `Http` client.
|
||||
|
||||
Internally, the `Provider` turns both the string and the class type into an `OpaqueToken`
|
||||
and keys its *token/provider* map with that.
|
||||
|
@ -709,8 +723,13 @@ code-example(format).
|
|||
+makeExample('dependency-injection/ts/app/injector.component.ts', 'injector',
|
||||
'app/injector.component.ts')
|
||||
:marked
|
||||
Angular injects the component's own `Injector` which the component uses to acquire services.
|
||||
The services themselves are not injected. They're retrieved via the injector.
|
||||
The `Injector` is itself an injectable service.
|
||||
|
||||
In this example, Angular injects the component's own `Injector` into the component's constructor.
|
||||
The component then asks the injected injector for the services it wants.
|
||||
|
||||
Note that the services themselves are not injected into the component.
|
||||
They are retrieved by calling `injector.get`.
|
||||
|
||||
The `get` method throws an error if it can't resolve the requested service.
|
||||
We can call `getOptional` instead, which we do in one case
|
||||
|
@ -718,14 +737,15 @@ code-example(format).
|
|||
|
||||
.l-sub-section
|
||||
:marked
|
||||
This technique is an example of the [Service Locator Pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||
The technique we just described is an example of the
|
||||
[Service Locator Pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
||||
|
||||
We **avoid** this technique unless we genuinely need it.
|
||||
It encourages a careless grab-bag approach such as we see here.
|
||||
It's difficult to explain, understand, and test.
|
||||
We can't know by inspecting the constructor what this class requires or what it will do.
|
||||
It could acquire services from any ancestor component, not just its own.
|
||||
We're forced to spelunk the implementation and hope for the best.
|
||||
It could acquire services from any ancestor component, not just its own.
|
||||
We're forced to spelunk the implementation to discover what it does.
|
||||
|
||||
Framework developers may take this approach when they
|
||||
must acquire services generically and dynamically.
|
||||
|
|
Loading…
Reference in New Issue