docs(dependency-injection): copy edit the TypeScript version
Also fix print style closes #826
This commit is contained in:
parent
d401716fc2
commit
4d9daf3493
|
@ -1,8 +1,8 @@
|
||||||
// #docregion
|
// #docregion
|
||||||
import {Engine, Tires, Car} from './car';
|
import {Engine, Tires, Car} from './car';
|
||||||
|
|
||||||
|
// BAD pattern!
|
||||||
export class CarFactory {
|
export class CarFactory {
|
||||||
|
|
||||||
createCar() {
|
createCar() {
|
||||||
let car = new Car(this.createEngine(), this.createTires());
|
let car = new Car(this.createEngine(), this.createTires());
|
||||||
car.description = 'Factory';
|
car.description = 'Factory';
|
||||||
|
|
|
@ -3,6 +3,6 @@ import {AppComponent} from './app.component';
|
||||||
import {HeroService} from './heroes/hero.service';
|
import {HeroService} from './heroes/hero.service';
|
||||||
|
|
||||||
//#docregion bootstrap
|
//#docregion bootstrap
|
||||||
// Injecting services in bootstrap works but is discouraged
|
bootstrap(AppComponent,
|
||||||
bootstrap(AppComponent, [HeroService]);
|
[HeroService]); // DISCOURAGED (but works)
|
||||||
//#enddocregion bootstrap
|
//#enddocregion bootstrap
|
||||||
|
|
|
@ -1,25 +1,23 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
:marked
|
:marked
|
||||||
**Dependency Injection** is an important application design pattern.
|
**Dependency injection** is an important application design pattern.
|
||||||
Angular has its own dependency injection framework and
|
Angular has its own dependency injection framework, and
|
||||||
we really can't build an Angular application without it.
|
we really can't build an Angular application without it.
|
||||||
It's used so widely that almost everyone just calls it "DI".
|
It's used so widely that almost everyone just calls it _DI_.
|
||||||
|
|
||||||
In this chapter we'll learn [what DI is, why](#why-di) we want it.
|
In this chapter we'll learn what DI is and why we want it.
|
||||||
Then we'll learn [how to use it](#angular-di) in an Angular app.
|
Then we'll learn [how to use it](#angular-di) in an Angular app.
|
||||||
|
|
||||||
All code in this chapter is available as a
|
[Run the live example](/resources/live-examples/dependency-injection/ts/plnkr.html)
|
||||||
[live example](/resources/live-examples/dependency-injection/ts/plnkr.html)
|
|
||||||
on the web.
|
|
||||||
|
|
||||||
<a id="why-di"></a>
|
<a id="why-di"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Why Dependency Injection?
|
## Why dependency injection?
|
||||||
|
|
||||||
Let's start with the following code.
|
Let's start with the following code.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/car/car-no-di.ts', 'car', 'app/car/car.ts (no di)')
|
+makeExample('dependency-injection/ts/app/car/car-no-di.ts', 'car', 'app/car/car.ts (without DI)')
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
Our `Car` creates everything it needs inside its constructor.
|
Our `Car` creates everything it needs inside its constructor.
|
||||||
|
@ -40,12 +38,12 @@ include ../../../../_includes/_util-fns
|
||||||
when the definion of `Engine` changes, our `Car` class must change.
|
when the definion of `Engine` changes, our `Car` class must change.
|
||||||
That makes `Car` brittle.
|
That makes `Car` brittle.
|
||||||
|
|
||||||
What if we want to put a different brand of tires on our `Car`. Too bad.
|
What if we want to put a different brand of tires on our `Car`? Too bad.
|
||||||
We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
|
We're locked into whatever brand the `Tires` class creates. That makes our `Car` inflexible.
|
||||||
|
|
||||||
Right now each new car gets its own engine. It can't share an engine with other cars.
|
Right now each new car gets its own engine. It can't share an engine with other cars.
|
||||||
While that makes sense for an automobile engine,
|
While that makes sense for an automobile engine,
|
||||||
we can think of other dependencies that should be shared ... like the onboard
|
we can think of other dependencies that should be shared, such as the onboard
|
||||||
wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
|
wireless connection to the manufacturer's service center. Our `Car` lacks the flexibility
|
||||||
to share services that have been created previously for other consumers.
|
to share services that have been created previously for other consumers.
|
||||||
|
|
||||||
|
@ -55,23 +53,23 @@ include ../../../../_includes/_util-fns
|
||||||
Will a new instance of `Engine` make an asynchronous call to the server?
|
Will a new instance of `Engine` make an asynchronous call to the server?
|
||||||
We certainly don't want that going on during our tests.
|
We certainly don't want that going on during our tests.
|
||||||
|
|
||||||
What if our `Car` should flash a warning signal when tire pressure is low.
|
What if our `Car` should flash a warning signal when tire pressure is low?
|
||||||
How do we confirm that it actually does flash a warning
|
How do we confirm that it actually does flash a warning
|
||||||
if we can't swap in low-pressure tires during the test?
|
if we can't swap in low-pressure tires during the test?
|
||||||
|
|
||||||
We have no control over the car's hidden dependencies.
|
We have no control over the car's hidden dependencies.
|
||||||
When we can't control the dependencies, a class become difficult to test.
|
When we can't control the dependencies, a class become difficult to test.
|
||||||
|
|
||||||
How can we make `Car` more robust, more flexible, and more testable?
|
How can we make `Car` more robust, flexible, and testable?
|
||||||
|
|
||||||
That's super easy. We probably already know what to do. We change our `Car` constructor to this:
|
That's super easy. We probably already know what to do. We change our `Car` constructor to a version with DI:
|
||||||
|
|
||||||
<a id="ctor-injection"></a>
|
<a id="ctor-injection"></a>
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
'dependency-injection/ts/app/car/car.ts, dependency-injection/ts/app/car/car-no-di.ts',
|
'dependency-injection/ts/app/car/car.ts, dependency-injection/ts/app/car/car-no-di.ts',
|
||||||
'car-ctor, car-ctor',
|
'car-ctor, car-ctor',
|
||||||
'app/car/car.ts DI (constructor), app/car/car.ts No DI (constructor)')(format=".")
|
'app/car/car.ts (excerpt with DI), app/car/car.ts (excerpt without DI)')(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
See what happened? We moved the definition of the dependencies to the constructor.
|
See what happened? We moved the definition of the dependencies to the constructor.
|
||||||
|
@ -84,7 +82,8 @@ include ../../../../_includes/_util-fns
|
||||||
:marked
|
:marked
|
||||||
Now we create a car by passing the engine and tires to the constructor.
|
Now we create a car by passing the engine and tires to the constructor.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation', 'Simple Car')(format=".")
|
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||||
|
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation', '', stylePattern)(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
How cool is that?
|
How cool is that?
|
||||||
|
@ -95,10 +94,11 @@ include ../../../../_includes/_util-fns
|
||||||
If someone extends the `Engine` class, that is not `Car`'s problem.
|
If someone extends the `Engine` class, that is not `Car`'s problem.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The consumer of `Car` has the problem. The consumer must update the car creation code to
|
The _consumer_ of `Car` has the problem. The consumer must update the car creation code to
|
||||||
something like:
|
something like this:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', 'Super Car')(format=".")
|
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||||
|
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-param', '', stylePattern)(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The critical point is this: `Car` itself did not have to change.
|
The critical point is this: `Car` itself did not have to change.
|
||||||
|
@ -110,10 +110,11 @@ include ../../../../_includes/_util-fns
|
||||||
We can pass mocks to the constructor that do exactly what we want them to do
|
We can pass mocks to the constructor that do exactly what we want them to do
|
||||||
during each test:
|
during each test:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', 'Test Car')(format=".")
|
- var stylePattern = { otl: /(new Car.*$)/gm };
|
||||||
|
+makeExample('dependency-injection/ts/app/car/car-creations.ts', 'car-ctor-instantiation-with-mocks', '', stylePattern)(format=".")
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
**We just learned what Dependency Injection is**.
|
**We just learned what dependency injection is**.
|
||||||
|
|
||||||
It's a coding pattern in which a class receives its dependencies from external
|
It's a coding pattern in which a class receives its dependencies from external
|
||||||
sources rather than creating them itself.
|
sources rather than creating them itself.
|
||||||
|
@ -131,17 +132,17 @@ include ../../../../_includes/_util-fns
|
||||||
:marked
|
:marked
|
||||||
It's not so bad now with only three creation methods.
|
It's not so bad now with only three creation methods.
|
||||||
But maintaining it will be hairy as the application grows.
|
But maintaining it will be hairy as the application grows.
|
||||||
This `SuperFactory` is going to become a huge spider web of
|
This factory is going to become a huge spiderweb of
|
||||||
interdependent factory methods!
|
interdependent factory methods!
|
||||||
|
|
||||||
Wouldn't it be nice if we could simply list the things we want to build without
|
Wouldn't it be nice if we could simply list the things we want to build without
|
||||||
having to define which dependency gets injected into what?
|
having to define which dependency gets injected into what?
|
||||||
|
|
||||||
This is where the Dependency Injection Framework comes into play.
|
This is where the dependency injection framework comes into play.
|
||||||
Imagine the framework had something called an `Injector`.
|
Imagine the framework had something called an _injector_.
|
||||||
We register some classes with this `Injector` and it figures out how to create them.
|
We register some classes with this injector, and it figures out how to create them.
|
||||||
|
|
||||||
When we need a `Car`, we simply ask the `Injector` to get it for us and we're good to go.
|
When we need a `Car`, we simply ask the injector to get it for us and we're good to go.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/car/car-injector.ts','injector-call')(format=".")
|
+makeExample('dependency-injection/ts/app/car/car-injector.ts','injector-call')(format=".")
|
||||||
|
|
||||||
|
@ -149,19 +150,19 @@ include ../../../../_includes/_util-fns
|
||||||
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
Everyone wins. The `Car` knows nothing about creating an `Engine` or `Tires`.
|
||||||
The consumer knows nothing about creating a `Car`.
|
The consumer knows nothing about creating a `Car`.
|
||||||
We don't have a gigantic factory class to maintain.
|
We don't have a gigantic factory class to maintain.
|
||||||
Both `Car` and consumer simply ask for what they need and the `Injector` delivers.
|
Both `Car` and consumer simply ask for what they need and the injector delivers.
|
||||||
|
|
||||||
This is what a **Dependency Injection Framework** is all about.
|
This is what a **dependency injection framework** is all about.
|
||||||
|
|
||||||
Now that we know what Dependency Injection is and appreciate its benefits,
|
Now that we know what dependency injection is and appreciate its benefits,
|
||||||
let's see how it is implemented in Angular.
|
let's see how it is implemented in Angular.
|
||||||
|
|
||||||
<a id="angular-di"></a>
|
<a id="angular-di"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Angular Dependency Injection
|
## Angular dependency injection
|
||||||
|
|
||||||
Angular ships with its own Dependency Injection framework. This framework can also be used
|
Angular ships with its own dependency injection framework. This framework can also be used
|
||||||
as a standalone module by other applications and frameworks.
|
as a standalone module by other applications and frameworks.
|
||||||
|
|
||||||
That sounds nice. What does it do for us when building components in Angular?
|
That sounds nice. What does it do for us when building components in Angular?
|
||||||
|
@ -183,19 +184,18 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The `HeroesComponent` is the root component of the *Heroes* feature area.
|
The `HeroesComponent` is the root component of the *Heroes* feature area.
|
||||||
It governs all the child components of this area.
|
It governs all the child components of this area.
|
||||||
In our stripped down version there is only one child, `HeroListComponent`,
|
Our stripped down version has only one child, `HeroListComponent`,
|
||||||
dedicated to displaying a list of heroes.
|
which displays a list of heroes.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Do we really need so many files? Of course not!
|
Do we really need so many files? Of course not!
|
||||||
We're going *beyond* the strictly necessary
|
We're going *beyond* the strictly necessary
|
||||||
in order to illustrate patterns that will serve us well in real applications.
|
in order to illustrate patterns that work well in real applications.
|
||||||
With each file we can make one or two points rather than crowd them all into one file.
|
|
||||||
:marked
|
:marked
|
||||||
Right now it gets heroes from `HEREOES`, an in-memory collection,
|
Right now `HeroListComponent` gets heroes from `HEROES`, an in-memory collection
|
||||||
defined in another file and imported by this component.
|
defined in another file and imported by this component.
|
||||||
That may suffice in the early stages of development but it's far from ideal.
|
That may suffice in the early stages of development, but it's far from ideal.
|
||||||
As soon as we try to test this component or want to get our heroes data from a remote server,
|
As soon as we try to test this component or want to get our heroes data from a remote server,
|
||||||
we'll have to change the implementation of `heroes` and
|
we'll have to change the implementation of `heroes` and
|
||||||
fix every other use of the `HEROES` mock data.
|
fix every other use of the `HEROES` mock data.
|
||||||
|
@ -207,83 +207,85 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.1.ts',null, 'app/heroes/hero.service.ts' )
|
+makeExample('dependency-injection/ts/app/heroes/hero.service.1.ts',null, 'app/heroes/hero.service.ts' )
|
||||||
:marked
|
:marked
|
||||||
Our `HeroService` exposes a `getHeroes()` method that returns
|
Our `HeroService` exposes a `getHeroes` method that returns
|
||||||
the same mock data as before but none of its consumers need to know that.
|
the same mock data as before, but none of its consumers need to know that.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
We aren't even pretending this is a real service.
|
We aren't even pretending this is a real service.
|
||||||
If we were actually getting data from a remote server, the API would have to be asynchronous,
|
If we were actually getting data from a remote server, the API would have to be asynchronous,
|
||||||
perhaps returning
|
perhaps returning
|
||||||
[ES2015 promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
|
[ES2015 promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).
|
||||||
We'd also have to rewrite the way components consume our service.
|
We'd also have to rewrite the way components consume our service.
|
||||||
This is important but not important to our current story.
|
This is important in general, but not to our current story.
|
||||||
:marked
|
:marked
|
||||||
A service is nothing more than a class in Angular 2.
|
A service is nothing more than a class in Angular 2.
|
||||||
It remains nothing more than a class until we register it with an Angular injector.
|
It remains nothing more than a class until we register it with an Angular injector.
|
||||||
|
|
||||||
### Configuring the Injector
|
### Configuring the injector
|
||||||
|
|
||||||
<a id="bootstrap"></a>
|
<a id="bootstrap"></a>
|
||||||
We don't have to create an Angular injector.
|
We don't have to create an Angular injector.
|
||||||
Angular creates an application-wide injector for us during the bootstrap process.
|
Angular creates an application-wide injector for us during the bootstrap process.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/main.ts', 'bootstrap', 'app/main.ts (bootstrap)')(format='.')
|
+makeExample('dependency-injection/ts/app/main.ts', 'bootstrap', 'app/main.ts (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We do have to configure the injector by registering the **providers**
|
We do have to configure the injector by registering the **providers**
|
||||||
that create the services we need in our application.
|
that create the services our application requires.
|
||||||
We'll explain what [providers](#providers) are later in this chapter.
|
We'll explain what [providers](#providers) are later in this chapter.
|
||||||
Before we do, let's see an example of provider registration during bootstrapping:
|
Before we do, let's see an example of provider registration during bootstrapping:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/main.1.ts', 'bootstrap')(format='.')
|
+makeExample('dependency-injection/ts/app/main.1.ts', 'bootstrap')(format='.')
|
||||||
:marked
|
:marked
|
||||||
The injector now knows about our `HeroService`.
|
The injector now knows about our `HeroService`.
|
||||||
An instance of our `HeroService` will be available for injection across our entire application.
|
An instance of our `HeroService` will be available for injection across our entire application.
|
||||||
|
|
||||||
Of course we can't help wondering about that comment telling us not to do it this way.
|
Of course we can't help wondering about that comment telling us not to do it this way.
|
||||||
It *will* work. It's just not a best practice.
|
It *will* work. It's just not a best practice.
|
||||||
The bootstrap provider option is intended for configuring and overriding Angular's own
|
The bootstrap provider option is intended for configuring and overriding Angular's own
|
||||||
pre-registered services.
|
preregistered services, such as its routing support.
|
||||||
|
|
||||||
The preferred approach is to register application providers in application components.
|
The preferred approach is to register application providers in application components.
|
||||||
Because the `HeroService` will be used within the *Heroes* feature area —
|
Because the `HeroService` is used within the *Heroes* feature area —
|
||||||
and nowhere else — the ideal place to register it is in the top-level `HeroesComponent`.
|
and nowhere else — the ideal place to register it is in the top-level `HeroesComponent`.
|
||||||
|
|
||||||
### Registering providers in a component
|
### Registering providers in a component
|
||||||
Here's a revised `HeroesComponent` that registers the `HeroService`.
|
Here's a revised `HeroesComponent` that registers the `HeroService`.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/heroes/heroes.component.1.ts',null,'app/heroes/heroes.component.ts')
|
+makeExample('dependency-injection/ts/app/heroes/heroes.component.1.ts',null,'app/heroes/heroes.component.ts')
|
||||||
:marked
|
:marked
|
||||||
Look closely at the bottom of the `@Component` metadata:
|
Look closely at the `providers` part of the `@Component` metadata:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/heroes/heroes.component.1.ts','providers')(format='.')
|
+makeExample('dependency-injection/ts/app/heroes/heroes.component.1.ts','providers')(format='.')
|
||||||
:marked
|
:marked
|
||||||
An instance of the `HeroService` is now available for injection in this `HeroesComponent`
|
An instance of the `HeroService` is now available for injection in this `HeroesComponent`
|
||||||
and all of its child components.
|
and all of its child components.
|
||||||
|
|
||||||
The `HeroesComponent` itself doesn't happen to need the `HeroService`.
|
The `HeroesComponent` itself doesn't happen to need the `HeroService`.
|
||||||
But its child `HeroListComponent` does so we head there next.
|
But its child `HeroListComponent` does, so we head there next.
|
||||||
|
|
||||||
### Preparing the *HeroListComponent* for injection
|
### Preparing the HeroListComponent for injection
|
||||||
|
|
||||||
The `HeroListComponent` should get heroes from the injected `HeroService`.
|
The `HeroListComponent` should get heroes from the injected `HeroService`.
|
||||||
Per the dependency injection pattern, the component must "ask for" the service in its constructor [as we explained
|
Per the dependency injection pattern, the component must ask for the service in its constructor, [as we explained
|
||||||
earlier](#ctor-injection).
|
earlier](#ctor-injection).
|
||||||
It's a small change as we see in this comparison:
|
It's a small change:
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
`dependency-injection/ts/app/heroes/hero-list.component.2.ts,
|
`dependency-injection/ts/app/heroes/hero-list.component.2.ts,
|
||||||
dependency-injection/ts/app/heroes/hero-list.component.1.ts`,
|
dependency-injection/ts/app/heroes/hero-list.component.1.ts`,
|
||||||
null,
|
null,
|
||||||
`app/heroes/hero-list.component (DI),
|
`app/heroes/hero-list.component (with DI),
|
||||||
app/heroes/hero-list.component (no DI)`)
|
app/heroes/hero-list.component (without DI)`)
|
||||||
:marked
|
:marked
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
### Focus on the constructor
|
### Focus on the constructor
|
||||||
+makeExample('dependency-injection/ts/app/heroes/hero-list.component.2.ts', 'ctor')(format=".")
|
|
||||||
:marked
|
|
||||||
Adding a parameter to the constructor isn't all that's happening here.
|
Adding a parameter to the constructor isn't all that's happening here.
|
||||||
|
|
||||||
|
+makeExample('dependency-injection/ts/app/heroes/hero-list.component.2.ts', 'ctor')(format=".")
|
||||||
|
|
||||||
|
:marked
|
||||||
We're writing in TypeScript and have followed the parameter name with a type annotation, `:HeroService`.
|
We're writing in TypeScript and have followed the parameter name with a type annotation, `:HeroService`.
|
||||||
The class is also decorated with the `@Component` decorator (scroll up to confirm that fact).
|
The class is also decorated with the `@Component` decorator (scroll up to confirm that fact).
|
||||||
|
|
||||||
|
@ -303,32 +305,32 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
We won't find code like that in the Tour of Heroes or any of our other samples.
|
We won't find code like that in the Tour of Heroes or any of our other samples.
|
||||||
We *could* write code [like that](#explicit-injector) if we *had* to which we rarely do.
|
We *could* write [code with an explicit injector](#explicit-injector) if we *had* to, but we rarely do.
|
||||||
Angular takes care of creating and calling injectors
|
Angular takes care of creating and calling injectors
|
||||||
when it creates components for us whether through HTML markup, as in `<hero-list></hero-list>`,
|
when it creates components for us — whether through HTML markup, as in `<hero-list></hero-list>`,
|
||||||
or after navigating to a component with the [router](./router.html).
|
or after navigating to a component with the [router](./router.html).
|
||||||
Let Angular do its job and we'll enjoy the benefits of automated dependency injection.
|
If we let Angular do its job, we'll enjoy the benefits of automated dependency injection.
|
||||||
|
|
||||||
### Singleton services
|
### Singleton services
|
||||||
Dependencies are singletons within the scope of an injector.
|
Dependencies are singletons within the scope of an injector.
|
||||||
In our example, there is a single `HeroService` instance shared among the
|
In our example, a single `HeroService` instance is shared among the
|
||||||
`HeroesComponent` and its `HeroListComponent` children.
|
`HeroesComponent` and its `HeroListComponent` children.
|
||||||
|
|
||||||
However, Angular DI is an hierarchical injection
|
However, Angular DI is an hierarchical injection
|
||||||
system which means nested injectors can create their own service instances.
|
system, which means that nested injectors can create their own service instances.
|
||||||
Learn more about that in the [Hierarchical Injection](./hierarchical-dependency-injection.html) chapter.
|
Learn more about that in the [Hierarchical Injectors](./hierarchical-dependency-injection.html) chapter.
|
||||||
|
|
||||||
### Testing the component
|
### Testing the component
|
||||||
We emphasized earlier that designing a class for dependency injection makes the class easier to test.
|
We emphasized earlier that designing a class for dependency injection makes the class easier to test.
|
||||||
Listing dependencies as constructor parameters may be all we need to test application parts effectively.
|
Listing dependencies as constructor parameters may be all we need to test application parts effectively.
|
||||||
|
|
||||||
For example, we can create a new `HeroListComponent` with a mock service that we can manipulate
|
For example, we can create a new `HeroListComponent` with a mock service that we can manipulate
|
||||||
under test:
|
under test:
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/test.component.ts', 'spec')(format='.')
|
+makeExample('dependency-injection/ts/app/test.component.ts', 'spec')(format='.')
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Learn more in the [Testing](../testing/index.html) chapter.
|
Learn more in [Testing](../testing/index.html).
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
### When the service needs a service
|
### When the service needs a service
|
||||||
|
@ -336,47 +338,47 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
|
|
||||||
What if it had a dependency? What if it reported its activities through a logging service?
|
What if it had a dependency? What if it reported its activities through a logging service?
|
||||||
We'd apply the same *constructor injection* pattern,
|
We'd apply the same *constructor injection* pattern,
|
||||||
adding a constructor that takes a `logger` parameter.
|
adding a constructor that takes a `Logger` parameter.
|
||||||
|
|
||||||
Here is the revision compared to the original.
|
Here is the revision compared to the original.
|
||||||
|
|
||||||
+makeTabs(
|
+makeTabs(
|
||||||
`dependency-injection/ts/app/heroes/hero.service.2.ts,
|
`dependency-injection/ts/app/heroes/hero.service.2.ts,
|
||||||
dependency-injection/ts/app/heroes/hero.service.1.ts`,
|
dependency-injection/ts/app/heroes/hero.service.1.ts`,
|
||||||
null,
|
null,
|
||||||
`app/heroes/hero.service (v.2),
|
`app/heroes/hero.service (v.2),
|
||||||
app/heroes/hero.service (v.1)`)
|
app/heroes/hero.service (v.1)`)
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `_logger`.
|
The constructor now asks for an injected instance of a `Logger` and stores it in a private property called `_logger`.
|
||||||
We call that property within our `getHeroes()` method when anyone asks for heroes.
|
We call that property within our `getHeroes` method when anyone asks for heroes.
|
||||||
|
|
||||||
<a id="injectable"></a>
|
<a id="injectable"></a>
|
||||||
### Why *@Injectable*?
|
### Why @Injectable?
|
||||||
Notice the `@Injectable()` decoration above the service class.
|
Notice the `@Injectable()` decoration above the service class.
|
||||||
We haven't seen `@Injectable()` before.
|
We haven't seen `@Injectable()` before.
|
||||||
As it happens, we could have added it to our first version`HeroService`.
|
As it happens, we could have added it to our first version of `HeroService`.
|
||||||
We didn't bother because we didn't need it then.
|
We didn't bother because we didn't need it then.
|
||||||
|
|
||||||
We need it now... now that our service has an injected dependency.
|
We need it now... now that our service has an injected dependency.
|
||||||
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`.
|
We need it because Angular requires constructor parameter metadata in order to inject a `Logger`.
|
||||||
As [we mentioned earlier](#di-metadata), TypeScript *only generates metadata for classes that have a decorator*. .
|
As [we mentioned earlier](#di-metadata), **TypeScript only generates metadata for classes that have a decorator.**
|
||||||
|
|
||||||
.callout.is-helpful
|
.callout.is-helpful
|
||||||
header Always add @Injectable()
|
header Always add @Injectable()
|
||||||
:marked
|
:marked
|
||||||
We recommend adding `@Injectable()` to every service class, even those that don't have dependencies
|
We recommend adding `@Injectable()` to every service class, even those that don't have dependencies
|
||||||
and, therefore, do not technically require it. Two reasons:
|
and, therefore, do not technically require it. Here's why:
|
||||||
ol(style="font-size:inherit")
|
ul(style="font-size:inherit")
|
||||||
li <i>Future proofing</i>: no need to remember <code>@Injectable</code> when we add a dependency later.
|
li <b>Future proofing:</b> No need to remember <code>@Injectable</code> when we add a dependency later.
|
||||||
li <i>Consistency</i>: all services follow the same rules and we don't have to wonder why a decorator is missing.
|
li <b>Consistency:</b> All services follow the same rules, and we don't have to wonder why a decorator is missing.
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`?
|
The `HeroesComponent` has an injected dependency too. Why don't we add `@Injectable()` to the `HeroesComponent`?
|
||||||
|
|
||||||
We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`.
|
We *can* add it if we really want to. It isn't necessary because the `HeroesComponent` is already decorated with `@Component`.
|
||||||
TypeScript generates metadata for *any* class with a decorator and *any* decorator will do.
|
TypeScript generates metadata for *any* class with a decorator, and *any* decorator will do.
|
||||||
|
|
||||||
.alert.is-critical
|
.alert.is-critical
|
||||||
:marked
|
:marked
|
||||||
|
@ -385,39 +387,39 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Create and register the *Logger* service
|
## Creating and registering a logger service
|
||||||
We're injecting a Logger into our `HeroService`.
|
We're injecting a logger into our `HeroService` in two steps:
|
||||||
We have two remaining steps:
|
1. Create the logger service.
|
||||||
1. Create the Logger service
|
1. Register it with the application.
|
||||||
1. Register it with the application
|
|
||||||
|
The logger service implementation is no big deal.
|
||||||
|
|
||||||
The logger service implementation is no big deal.
|
|
||||||
|
|
||||||
+makeExample(
|
+makeExample(
|
||||||
'dependency-injection/ts/app/logger.service.ts',null, 'app/logger.service')
|
'dependency-injection/ts/app/logger.service.ts',null, 'app/logger.service')
|
||||||
:marked
|
:marked
|
||||||
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-logger', 'app/app.component.ts (providers)')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-logger', 'app/app.component.ts (excerpt)')
|
||||||
:marked
|
:marked
|
||||||
If we forget to register it, Angular throws an exception when it first looks for the logger:
|
If we forget to register the logger, Angular throws an exception when it first looks for the logger:
|
||||||
code-example(format).
|
code-example(format, language="html").
|
||||||
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)
|
||||||
:marked
|
:marked
|
||||||
That's Angular 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.
|
||||||
It needed that provider to create a `Logger` to inject into a new `HeroService` which it needed to
|
It needed that provider to create a `Logger` to inject into a new
|
||||||
create and inject into a new `HeroListComponent`.
|
`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.
|
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 can do that.
|
We'd use it if we had it, ignore it if we didn't. We can do that.
|
||||||
|
|
||||||
First 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
|
||||||
|
@ -426,40 +428,40 @@ code-example(format).
|
||||||
+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.
|
the injector will inject `null`. We have a method that logs.
|
||||||
What can we do to avoid a null reference exception?
|
What can we do to avoid a null reference exception?
|
||||||
|
|
||||||
We could substitute a *do-nothing* logger stub so that calling 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
|
||||||
in multiple locations.
|
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*,
|
||||||
as we learn in the next section.
|
as we learn in the next section.
|
||||||
|
|
||||||
<a id="providers"></a>
|
<a id="providers"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Injector Providers
|
## Injector providers
|
||||||
|
|
||||||
A provider *provides* the concrete, runtime version of a dependency value.
|
A provider *provides* the concrete, runtime version of a dependency value.
|
||||||
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 the injector injects into components and other services.
|
||||||
|
|
||||||
|
We must register a service *provider* with the injector, or it won't know how to create the service.
|
||||||
|
|
||||||
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 a service class.
|
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.
|
In reality it holds an instance of the [Provider](/docs/ts/latest/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`.
|
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.
|
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.
|
||||||
|
|
||||||
We can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
|
We can configure the injector with alternative providers that can deliver an object that behaves like a `Logger`.
|
||||||
We could provide a substitute class. We could provide a logger-like object.
|
We could provide a substitute class. We could provide a logger-like object.
|
||||||
We could give it a provider that calls a logger factory function.
|
We could give it a provider that calls a logger factory function.
|
||||||
|
@ -477,114 +479,115 @@ code-example(format).
|
||||||
[Provider](/docs/ts/latest/api/core/Provider-class.html) class.
|
[Provider](/docs/ts/latest/api/core/Provider-class.html) class.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-2')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-2')
|
||||||
:marked
|
:marked
|
||||||
The [provide](../api/core/provide-function.html) function is the more common and friendlier way to create a `Provider`:
|
The [provide](/docs/ts/latest/api/core/provide-function.html) function is the more common, friendlier way to create a `Provider`:
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-3')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-3')
|
||||||
:marked
|
:marked
|
||||||
In both approaches — `Provider` class and `provide` function —
|
In both approaches — `Provider` class and `provide` function —
|
||||||
we supply two arguments.
|
we supply two arguments.
|
||||||
|
|
||||||
The first is the [token](#token) that serves as the key for both locating a dependency value
|
The first is the [token](#token) that serves as the key for both locating a dependency value
|
||||||
and registering the provider.
|
and registering the provider.
|
||||||
|
|
||||||
The second is a provider definition object
|
The second is a provider definition object,
|
||||||
which we think of as a *recipe* for creating the dependency value.
|
which we can think of as a *recipe* for creating the dependency value.
|
||||||
There are many ways to create dependency values ... and many ways to write a recipe.
|
There are many ways to create dependency values... and many ways to write a recipe.
|
||||||
|
|
||||||
<a id="class-provider"></a>
|
<a id="class-provider"></a>
|
||||||
### Alternative Class Providers
|
### Alternative class providers
|
||||||
|
|
||||||
Occasionally we'll ask a different class to provide the service.
|
Occasionally we'll ask a different class to provide the service.
|
||||||
In this example, we tell the injector
|
The following code tells the injector
|
||||||
to return a `BetterLogger` when something asks for the `Logger`.
|
to return a `BetterLogger` when something asks for the `Logger`.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-4')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-4')
|
||||||
:marked
|
:marked
|
||||||
### Class provider with dependencies
|
### Class provider with dependencies
|
||||||
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
Maybe an `EvenBetterLogger` could display the user name in the log message.
|
||||||
This logger gets the user from the injected `UserService`
|
This logger gets the user from the injected `UserService`,
|
||||||
which happens also to be injected at the application level.
|
which happens also to be injected at the application level.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','EvenBetterLogger')
|
+makeExample('dependency-injection/ts/app/providers.component.ts','EvenBetterLogger')
|
||||||
:marked
|
:marked
|
||||||
Configure it like we did `BetterLogger`.
|
Configure it like we did `BetterLogger`.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-5')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-5')(format=".")
|
||||||
:marked
|
:marked
|
||||||
### Aliased Class Providers
|
### Aliased class providers
|
||||||
|
|
||||||
Suppose there is an old component that depends upon an `OldLogger` class.
|
Suppose an old component 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`,
|
||||||
we want the one singleton instance of `NewLogger` to handle it instead.
|
we want the singleton instance of `NewLogger` to handle it instead.
|
||||||
|
|
||||||
The dependency injector should inject that singleton instance
|
The dependency injector should inject that singleton instance
|
||||||
when a component asks for either the new or the the old logger.
|
when a component asks for either the new or the the old logger.
|
||||||
The `OldLogger` should be an alias for `NewLogger`.
|
The `OldLogger` should be an alias for `NewLogger`.
|
||||||
|
|
||||||
We certainly do not want two different `NewLogger` instances in our app.
|
We certainly do not want two different `NewLogger` instances in our app.
|
||||||
Unfortunately, that's what we get if we try to alias `OldLogger` to `NewLogger` with `useClass`.
|
Unfortunately, that's what we get if we try to alias `OldLogger` to `NewLogger` with `useClass`.
|
||||||
|
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6a')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6a')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Alias with the `useExisting` option instead.
|
The solution: Alias with the `useExisting` option.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6b')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-6b')(format=".")
|
||||||
|
|
||||||
<a id="value-provider"></a>
|
<a id="value-provider"></a>
|
||||||
:marked
|
:marked
|
||||||
### Value Providers
|
### Value providers
|
||||||
|
|
||||||
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
Sometimes it's easier to provide a ready-made object rather than ask the injector to create it from a class.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','silent-logger')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','silent-logger')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Then we register a provider with this object playing the logger role via the `useValue` option.
|
Then we register a provider with the `useValue` option,
|
||||||
|
which makes this object play the logger role.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-7')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-7')(format=".")
|
||||||
|
|
||||||
<a id="factory-provider"></a>
|
<a id="factory-provider"></a>
|
||||||
:marked
|
:marked
|
||||||
### Factory Providers
|
### Factory providers
|
||||||
|
|
||||||
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 keeps changes repeatedly in the course of the browser session..
|
Maybe the information 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**.
|
This situation calls for a **factory provider**.
|
||||||
|
|
||||||
Let's illustrate by adding a new business requirement:
|
Let's illustrate by adding a new business requirement:
|
||||||
the HeroService must hide *secret* heroes from normal users.
|
The HeroService must hide *secret* heroes from normal users.
|
||||||
Only authorized users should see secret heroes.
|
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.
|
||||||
That authorization can change during the course of a single application session
|
That authorization can change during the course of a single application session,
|
||||||
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
|
The `HeroService` won't have direct access to the user information to decide
|
||||||
who is authoriazed and who is not.
|
who is authorized 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.
|
||||||
:marked
|
:marked
|
||||||
Instead the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
Instead the `HeroService` constructor takes a boolean flag to control display of secret heroes.
|
||||||
+makeExample('dependency-injection/ts/app/heroes/hero.service.ts','internals', 'app/heroes/hero.service.ts (internals)')(format='.')
|
+makeExample('dependency-injection/ts/app/heroes/hero.service.ts','internals', 'app/heroes/hero.service.ts (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We can inject the `Logger` but we can't inject the boolean `isAuthorized`.
|
We can inject the `Logger`, but we can't inject the boolean `isAuthorized`.
|
||||||
We'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
We'll have to take over the creation of new instances of this `HeroService` with a factory provider.
|
||||||
|
|
||||||
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 (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Although the `HeroService` has no access to 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 and let the injector pass them along to the factory function:
|
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 (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The `useFactory` field tells Angular that the provider is a factory function
|
The `useFactory` field tells Angular that the provider is a factory function
|
||||||
whose implementation is the `heroServiceFactory`.
|
whose implementation is the `heroServiceFactory`.
|
||||||
|
|
||||||
The `deps` property is an array of [provider tokens](#token).
|
The `deps` property is an array of [provider tokens](#token).
|
||||||
|
@ -592,55 +595,56 @@ code-example(format).
|
||||||
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
The injector resolves these tokens and injects the corresponding services into the matching factory function parameters.
|
||||||
:marked
|
:marked
|
||||||
Notice that we 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 the factory provider re-usable.
|
This extra step makes the factory provider reusable.
|
||||||
We can register our `HeroService` with this variable whereever we need it.
|
We can register our `HeroService` with this variable wherever 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:
|
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`,
|
||||||
null,
|
null,
|
||||||
`app/heroes/heroes.component (v.3),
|
`app/heroes/heroes.component (v.3),
|
||||||
app/heroes/heroes.component (v.2)`)
|
app/heroes/heroes.component (v.2)`)
|
||||||
|
|
||||||
<a id="token"></a>
|
<a id="token"></a>
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Dependency Injection Tokens
|
## Dependency injection tokens
|
||||||
|
|
||||||
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. The token is the key to the map.
|
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 key.
|
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.
|
Here we get a `HeroService` directly from the injector by supplying the `HeroService` type as the 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 when we write a constructor that requires an injected class-based dependency.
|
||||||
We define a constructor parameter with the `HeroService` class type and Angular knows to inject the
|
We define a constructor parameter with the `HeroService` class type,
|
||||||
|
and Angular knows to inject the
|
||||||
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 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
|
||||||
|
|
||||||
What if the dependency value isn't a class?
|
What if the dependency value isn't a class?
|
||||||
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 tend to be object hashes 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 (excerpt)')(format='.')
|
||||||
:marked
|
:marked
|
||||||
We'd like to make this `config` object available for injection.
|
We'd like to make this `config` object available for injection.
|
||||||
We know we can register an object with a [Value Provider](#value-provider).
|
We know we can register an object with a [value provider](#value-provider).
|
||||||
But what do we use for the token?
|
But what do we use for the token?
|
||||||
We don't have a class to serve as a token. There is no `Config` class.
|
We don't have a class to serve as a token. There is no `Config` class.
|
||||||
|
|
||||||
// begin Typescript only
|
// begin Typescript only
|
||||||
<a id="interface"></a>
|
<a id="interface"></a>
|
||||||
:marked
|
:marked
|
||||||
|
@ -651,11 +655,11 @@ code-example(format).
|
||||||
+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
|
||||||
That seems strange if we're used to dependency injection in strongly-typed languages where
|
That seems strange if we're used to dependency injection in strongly typed languages, where
|
||||||
an interface is the preferred dependency lookup key.
|
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.
|
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.
|
The TypeScript interface disappears from the generated JavaScript.
|
||||||
There is no interface type information left for Angular to find at runtime.
|
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>
|
||||||
|
@ -664,29 +668,29 @@ code-example(format).
|
||||||
Fortunately, we can register any dependency provider with a string token.
|
Fortunately, we can register any dependency provider with a string token.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9a')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Now we inject the configuration object into any constructor that needs it with
|
Now we inject the configuration object into any constructor that needs it, with
|
||||||
the help of an `@Inject` decorator to tell Angular how to find the configuration dependency value.
|
the help of an `@Inject` decorator that tells Angular how to find the configuration dependency value.
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9a-ctor')(format=".")
|
||||||
:marked
|
:marked
|
||||||
|
|
||||||
// begin Typescript only
|
// begin Typescript only
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Although it plays no role in dependency injection,
|
Although it plays no role in dependency injection,
|
||||||
the `Config` interface supports strongly-typing of the configuration object within the class.
|
the `Config` interface supports strong typing of the configuration object within the class.
|
||||||
:marked
|
:marked
|
||||||
// end typescript only
|
// end typescript only
|
||||||
|
|
||||||
<a id="opaque-token"></a>
|
<a id="opaque-token"></a>
|
||||||
:marked
|
:marked
|
||||||
### OpaqueToken
|
### OpaqueToken
|
||||||
Alternatively, we could define and use an [OpaqueToken](../api/core/OpaqueToken-class.html)
|
Alternatively, we could define and use an [OpaqueToken](/docs/ts/latest/api/core/OpaqueToken-class.html)
|
||||||
rather than rely on magic strings that may collide with other developers' string choices.
|
rather than rely on magic strings that may collide with other developers' string choices.
|
||||||
|
|
||||||
The definition looks like this:
|
The definition looks like this:
|
||||||
+makeExample('dependency-injection/ts/app/app.config.ts','token')(format='.')
|
+makeExample('dependency-injection/ts/app/app.config.ts','token')(format='.')
|
||||||
:marked
|
:marked
|
||||||
Substitute `APP_CONFIG` for the magic string in provider registration and constructor:
|
Substitute `APP_CONFIG` for the magic string when registering the provider and defining the constructor parameter:
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9b')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','providers-9b')(format=".")
|
||||||
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9b-ctor')(format=".")
|
+makeExample('dependency-injection/ts/app/providers.component.ts','provider-9b-ctor')(format=".")
|
||||||
:marked
|
:marked
|
||||||
|
@ -696,69 +700,72 @@ code-example(format).
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
Angular itself uses `OpaqueTokens` to register all of its own non-class dependencies. For example,
|
Angular itself uses `OpaqueToken` objects 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)
|
[HTTP_PROVIDERS](/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.
|
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.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
# Next Steps
|
## Summary
|
||||||
We learned the basics of Angular Dependency Injection in this chapter.
|
We learned the basics of Angular dependency injection in this chapter.
|
||||||
|
We can register various kinds of providers,
|
||||||
|
and we know how to ask for an injected object (such as a service) by
|
||||||
|
adding a parameter to a constructor.
|
||||||
|
|
||||||
The Angular Dependency Injection is more capable than we've described.
|
Angular dependency injection is more capable than we've described.
|
||||||
We can learn more about its advanced features, beginning with its support for
|
We can learn more about its advanced features, beginning with its support for
|
||||||
nested injectors, in the
|
nested injectors, in the
|
||||||
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) chapter.
|
[Hierarchical Dependency Injection](hierarchical-dependency-injection.html) chapter.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
<a id="explicit-injector"></a>
|
<a id="explicit-injector"></a>
|
||||||
:marked
|
:marked
|
||||||
### Appendix: Working with injectors directly
|
### Appendix: Working with injectors directly
|
||||||
We rarely work directly with an injector.
|
We rarely work directly with an injector.
|
||||||
Here's an `InjectorComponent` that does.
|
Here's an `InjectorComponent` that does.
|
||||||
|
|
||||||
+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
|
||||||
The `Injector` is itself an injectable service.
|
The `Injector` is itself an injectable service.
|
||||||
|
|
||||||
In this example, Angular injects the component's own `Injector` into the component's constructor.
|
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.
|
The component then asks the injected injector for the services it wants.
|
||||||
|
|
||||||
Note that the services themselves are not injected into the component.
|
Note that the services themselves are not injected into the component.
|
||||||
They are retrieved by calling `injector.get`.
|
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
|
||||||
to retrieve a service (`ROUS`) that isn't registered with this or any ancestor injector.
|
to retrieve a service (`ROUS`) that isn't registered with this or any ancestor injector.
|
||||||
|
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
The technique we just described is an example of the
|
The technique we just described is an example of the
|
||||||
[Service Locator Pattern](https://en.wikipedia.org/wiki/Service_locator_pattern).
|
[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 to discover what it does.
|
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.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
<a id="forward-ref"></a>
|
<a id="forward-ref"></a>
|
||||||
:marked
|
:marked
|
||||||
### Appendix: Why we recommend one class per file
|
### Appendix: Why we recommend one class per file
|
||||||
|
|
||||||
Having multiple classes in the same file is confusing and best avoided.
|
Having multiple classes in the same file is confusing and best avoided.
|
||||||
Developers expect one class per file. Keep them happy.
|
Developers expect one class per file. Keep them happy.
|
||||||
|
|
||||||
If we scorn this advice and, say,
|
If we scorn this advice and, say,
|
||||||
combine our `HeroService` class with the `HeroesComponent` in the same file,
|
combine our `HeroService` class with the `HeroesComponent` in the same file,
|
||||||
**define the component last!**
|
**define the component last!**
|
||||||
If we define the component before the service,
|
If we define the component before the service,
|
||||||
|
@ -769,4 +776,4 @@ code-example(format).
|
||||||
We actually can define the component first with the help of the `forwardRef()` method as explained
|
We actually can define the component first with the help of the `forwardRef()` method as explained
|
||||||
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
in this [blog post](http://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html).
|
||||||
But why flirt with trouble?
|
But why flirt with trouble?
|
||||||
Avoid the problem altogether and define components and services in separate files.
|
Avoid the problem altogether by defining components and services in separate files.
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
@media print {
|
@media print {
|
||||||
.hero::after {
|
/*
|
||||||
content: "Developer Preview Only - some details may change";
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Print Styles
|
* Print Styles
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue