parent
12d4e17f94
commit
ab6d362650
|
@ -7,7 +7,9 @@ import {Logger} from '../logger.service';
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class HeroService {
|
export class HeroService {
|
||||||
|
|
||||||
|
//#docregion ctor
|
||||||
constructor(private _logger: Logger) { }
|
constructor(private _logger: Logger) { }
|
||||||
|
//#enddocregion ctor
|
||||||
|
|
||||||
getHeroes() {
|
getHeroes() {
|
||||||
this._logger.log('Getting heroes ...')
|
this._logger.log('Getting heroes ...')
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Examples of provider arrays
|
// Examples of provider arrays
|
||||||
|
//#docplaster
|
||||||
import { Component, Host, Inject, Injectable,
|
import { Component, Host, Inject, Injectable,
|
||||||
provide, Provider} from 'angular2/core';
|
provide, Provider} from 'angular2/core';
|
||||||
|
|
||||||
|
@ -103,10 +104,8 @@ class EvenBetterLogger {
|
||||||
template: template,
|
template: template,
|
||||||
providers:
|
providers:
|
||||||
//#docregion providers-5
|
//#docregion providers-5
|
||||||
[
|
[ UserService,
|
||||||
UserService,
|
provide(Logger, {useClass: EvenBetterLogger}) ]
|
||||||
provide(Logger, {useClass: EvenBetterLogger})
|
|
||||||
]
|
|
||||||
//#enddocregion providers-5
|
//#enddocregion providers-5
|
||||||
})
|
})
|
||||||
export class ProviderComponent5 {
|
export class ProviderComponent5 {
|
||||||
|
@ -131,11 +130,9 @@ class OldLogger {
|
||||||
template: template,
|
template: template,
|
||||||
providers:
|
providers:
|
||||||
//#docregion providers-6a
|
//#docregion providers-6a
|
||||||
[
|
[ NewLogger,
|
||||||
NewLogger,
|
|
||||||
// Not aliased! Creates two instances of `NewLogger`
|
// Not aliased! Creates two instances of `NewLogger`
|
||||||
provide(OldLogger, {useClass:NewLogger})
|
provide(OldLogger, {useClass:NewLogger}) ]
|
||||||
]
|
|
||||||
//#enddocregion providers-6a
|
//#enddocregion providers-6a
|
||||||
})
|
})
|
||||||
export class ProviderComponent6a {
|
export class ProviderComponent6a {
|
||||||
|
@ -156,11 +153,9 @@ export class ProviderComponent6a {
|
||||||
template: template,
|
template: template,
|
||||||
providers:
|
providers:
|
||||||
//#docregion providers-6b
|
//#docregion providers-6b
|
||||||
[
|
[ NewLogger,
|
||||||
NewLogger,
|
|
||||||
// Alias OldLogger w/ reference to NewLogger
|
// Alias OldLogger w/ reference to NewLogger
|
||||||
provide(OldLogger, {useExisting: NewLogger})
|
provide(OldLogger, {useExisting: NewLogger}) ]
|
||||||
]
|
|
||||||
//#enddocregion providers-6b
|
//#enddocregion providers-6b
|
||||||
})
|
})
|
||||||
export class ProviderComponent6b {
|
export class ProviderComponent6b {
|
||||||
|
@ -306,7 +301,9 @@ export class ProviderComponent10b {
|
||||||
log: (msg:string)=> this._logger.logs.push(msg),
|
log: (msg:string)=> this._logger.logs.push(msg),
|
||||||
logs: []
|
logs: []
|
||||||
}
|
}
|
||||||
|
// #enddocregion provider-10-logger
|
||||||
this._logger.log("Optional logger was not available.")
|
this._logger.log("Optional logger was not available.")
|
||||||
|
// #docregion provider-10-logger
|
||||||
}
|
}
|
||||||
// #enddocregion provider-10-logger
|
// #enddocregion provider-10-logger
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -399,38 +399,41 @@ include ../../../../_includes/_util-fns
|
||||||
We're likely to need the same logger service everywhere in our application
|
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
|
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`.
|
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
|
: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).
|
code-example(format).
|
||||||
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
||||||
:marked
|
:marked
|
||||||
That's telling us that the dependency injector couldn't find the *provider* for the logger
|
That's Angular 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`
|
It needed that provider to create a `Logger` to inject into a new `HeroService` which it needed to
|
||||||
was calling for heroes.
|
create and inject into a new `HeroListComponent`.
|
||||||
The *provider* is the subject of our next section.
|
|
||||||
|
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?
|
But wait! What if the logger is optional?
|
||||||
<a id="optional"></a>
|
<a id="optional"></a>
|
||||||
### Optional dependencies
|
### Optional dependencies
|
||||||
|
|
||||||
Our `HeroService` currently requires a `Logger`. What if we could get by without a logger?
|
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='.')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','import-optional')(format='.')
|
||||||
:marked
|
: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='.')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-ctor')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Be prepared for a null logger. If we don't register one somewhere up the line,
|
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='.')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-10-logger')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Obviously we'd take a more sophisticated approach if the logger were optional
|
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.
|
But enough about optional loggers. In our sample application, the `Logger` is required.
|
||||||
We must register a `Logger` with the application injector using *providers*
|
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
|
The injector relies on **providers** to create instances of the services
|
||||||
that it injects into components and other 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:
|
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')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger')
|
||||||
:marked
|
:marked
|
||||||
The `providers` array appears to hold service classes (one service class in this example).
|
The `providers` array appears to hold a service class.
|
||||||
In reality it holds instances of the [Provider](../api/core/Provider-class.html) class.
|
In reality it holds an instance the [Provider](../api/core/Provider-class.html) class that can create that service.
|
||||||
|
|
||||||
In our example, when the `HeroService` constructor specifies `logger:Logger`, it's expecting
|
There are many ways to *provide* something that looks and behaves like a `Logger`.
|
||||||
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 `Logger` class itself is an obvious and natural provider - it has the right shape and it's designed to be created.
|
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.
|
But it's not the only way.
|
||||||
|
|
||||||
|
@ -511,7 +511,7 @@ code-example(format).
|
||||||
### Aliased Class Providers
|
### Aliased Class Providers
|
||||||
|
|
||||||
Suppose there is an old component that depends upon an `OldLogger` class.
|
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.
|
we can't update the old component to use it.
|
||||||
|
|
||||||
When the *old* component logs a message with `OldLogger`,
|
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,
|
Sometimes we need to create the dependent value dynamically,
|
||||||
based on information we won't have until the last possible moment.
|
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.
|
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.
|
Let's illustrate by adding a new business requirement:
|
||||||
Only authorized users should see them.
|
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.
|
Like the `EvenBetterLogger`, the `HeroService` needs a fact about the user.
|
||||||
It needs to know if the user is authorized to see secret heroes.
|
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.
|
as when we log in a different user.
|
||||||
|
|
||||||
Unlike `EvenBetterLogger`, we can't inject the `UserService` into the `HeroService`.
|
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
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Why? We don't know either. Stuff like this happens.
|
Why? We don't know either. Stuff like this happens.
|
||||||
|
@ -573,9 +576,9 @@ code-example(format).
|
||||||
A factory provider needs a factory function:
|
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='.')
|
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','factory', 'app/heroes/hero.service.provider.ts (factory)')(format='.')
|
||||||
:marked
|
: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='.')
|
+makeExample('dependency-injection/ts/app/heroes/hero.service.provider.ts','provider', 'app/heroes/hero.service.provider.ts (provider)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
|
|
||||||
|
@ -586,12 +589,15 @@ code-example(format).
|
||||||
|
|
||||||
The `deps` property is an array of [provider tokens](#token).
|
The `deps` property is an array of [provider tokens](#token).
|
||||||
The `Logger` and `UserService` classes serve as tokens for their own class providers.
|
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
|
:marked
|
||||||
Notice that we've captured the factory provider in an exported variable, `heroServiceProvider`.
|
Notice that we 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.
|
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`
|
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(
|
+makeTabs(
|
||||||
`dependency-injection/ts/app/heroes/heroes.component.ts,
|
`dependency-injection/ts/app/heroes/heroes.component.ts,
|
||||||
dependency-injection/ts/app/heroes/heroes.component.1.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.
|
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
|
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
|
In all previous examples, the dependency value has been a class *instance* and
|
||||||
the class *type* served as its own lookup 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 token.
|
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='.')
|
+makeExample('dependency-injection/ts/app/injector.component.ts','get-hero-service')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We have similar good fortune (in typescript) when we write a constructor that requires an injected class-based dependency.
|
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:
|
service associated with that `HeroService` class token:
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-8-ctor')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-8-ctor')(format=".")
|
||||||
:marked
|
: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
|
### 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.
|
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.
|
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='.')
|
+makeExample('dependency-injection/ts/app/app.config.ts','config','app/app-config.ts')(format='.')
|
||||||
:marked
|
:marked
|
||||||
|
@ -638,13 +644,19 @@ code-example(format).
|
||||||
// begin Typescript only
|
// begin Typescript only
|
||||||
<a id="interface"></a>
|
<a id="interface"></a>
|
||||||
:marked
|
:marked
|
||||||
|
### Interfaces aren't valid tokens
|
||||||
|
|
||||||
The `CONFIG` constant has an interface, `Config`. Unfortunately, we
|
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','providers-9a-interface')(format=".")
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor-interface')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor-interface')(format=".")
|
||||||
:marked
|
:marked
|
||||||
It's not Angular's fault. An interface is a TypeScript design-time artifact.
|
That seems strange if we're used to dependency injection in strongly-typed languages where
|
||||||
It disappears from the generated JavaScript so there is no interface type information for Angular to find at runtime.
|
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
|
// end Typescript only
|
||||||
<a id="string-token"></a>
|
<a id="string-token"></a>
|
||||||
:marked
|
:marked
|
||||||
|
@ -684,7 +696,9 @@ code-example(format).
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
: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`
|
Internally, the `Provider` turns both the string and the class type into an `OpaqueToken`
|
||||||
and keys its *token/provider* map with that.
|
and keys its *token/provider* map with that.
|
||||||
|
@ -709,8 +723,13 @@ code-example(format).
|
||||||
+makeExample('dependency-injection/ts/app/injector.component.ts', 'injector',
|
+makeExample('dependency-injection/ts/app/injector.component.ts', 'injector',
|
||||||
'app/injector.component.ts')
|
'app/injector.component.ts')
|
||||||
:marked
|
:marked
|
||||||
Angular injects the component's own `Injector` which the component uses to acquire services.
|
The `Injector` is itself an injectable service.
|
||||||
The services themselves are not injected. They're retrieved via the injector.
|
|
||||||
|
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.
|
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
|
We can call `getOptional` instead, which we do in one case
|
||||||
|
@ -718,14 +737,15 @@ code-example(format).
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
: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.
|
We **avoid** this technique unless we genuinely need it.
|
||||||
It encourages a careless grab-bag approach such as we see here.
|
It encourages a careless grab-bag approach such as we see here.
|
||||||
It's difficult to explain, understand, and test.
|
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.
|
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.
|
It could acquire services from any ancestor component, not just its own.
|
||||||
We're forced to spelunk the implementation and hope for the best.
|
We're forced to spelunk the implementation to discover what it does.
|
||||||
|
|
||||||
Framework developers may take this approach when they
|
Framework developers may take this approach when they
|
||||||
must acquire services generically and dynamically.
|
must acquire services generically and dynamically.
|
||||||
|
|
Loading…
Reference in New Issue