docs(Hierarchical DI): code shuffle, text, & alpha52 updates

closes #480
This commit is contained in:
Ward Bell 2015-12-11 13:18:37 -08:00
parent 4084a725fd
commit b2cae850d8
14 changed files with 103 additions and 75 deletions

View File

@ -0,0 +1,13 @@
// #docregion
import {bootstrap} from 'angular2/platform/browser';
import {HeroesListComponent} from './heroes-list.component';
import {HeroesService} from './heroes.service';
bootstrap(HeroesListComponent, [HeroesService])
/* Documentation artifact below
// #docregion bad-alternative
// Don't do this!
bootstrap(HeroesListComponent, [HeroesService, RestoreService])
// #enddocregion bad-alternative
*/

View File

@ -1,5 +1,5 @@
// #docregion // #docregion
import {Component, Input} from 'angular2/angular2'; import {Component, Input} from 'angular2/core';
import {Hero} from './hero'; import {Hero} from './hero';
@Component({ @Component({

View File

@ -1,6 +1,6 @@
// #docregion // #docregion
import {Component, Input, Output, EventEmitter} from 'angular2/angular2'; import {Component, Input, Output, EventEmitter} from 'angular2/core';
import {RestoreService} from './restore-service'; import {RestoreService} from './restore.service';
import {Hero} from './hero'; import {Hero} from './hero';
@Component({ @Component({
@ -11,7 +11,7 @@ import {Hero} from './hero';
template: ` template: `
<div> <div>
<span>Name:</span> <span>Name:</span>
<input [(ng-model)]="hero.name"/> <input [(ngModel)]="hero.name"/>
<div> <div>
<button (click)="onSaved()">save</button> <button (click)="onSaved()">save</button>
<button (click)="onCanceled()">cancel</button> <button (click)="onCanceled()">cancel</button>

View File

@ -1,7 +1,7 @@
// #docregion // #docregion
import {Component, bootstrap} from 'angular2/angular2'; import {Component} from 'angular2/core';
import {EditItem} from './edit-item'; import {EditItem} from './edit-item';
import {HeroesService} from './heroes-service'; import {HeroesService} from './heroes.service';
import {HeroCardComponent} from './hero-card.component'; import {HeroCardComponent} from './hero-card.component';
import {HeroEditorComponent} from './hero-editor.component'; import {HeroEditorComponent} from './hero-editor.component';
import {Hero} from './hero'; import {Hero} from './hero';
@ -11,7 +11,7 @@ import {Hero} from './hero';
template: ` template: `
<div> <div>
<ul> <ul>
<li *ng-for="#editItem of heroes"> <li *ngFor="#editItem of heroes">
<hero-card <hero-card
[hidden]="editItem.editing" [hidden]="editItem.editing"
[hero]="editItem.item"> [hero]="editItem.item">
@ -49,5 +49,5 @@ export class HeroesListComponent {
} }
} }
bootstrap(HeroesListComponent, [HeroesService]);
// #enddocregion // #enddocregion

View File

@ -3,13 +3,13 @@
<head> <head>
<title>Hierarchical Injectors</title> <title>Hierarchical Injectors</title>
<script src="../node_modules/systemjs/dist/system.src.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script> <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
<script> <script>
System.config({ System.config({
packages: {'app': {defaultExtension: 'js'}} packages: {'app': {defaultExtension: 'js'}}
}); });
System.import('app/heroes-list.component'); System.import('app/boot');
</script> </script>
</head> </head>

View File

@ -1,21 +1,15 @@
{ {
"name": "angular2-getting-started", "name": "angular2-hierarchical-injection",
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"tsc": "tsc -p src -w", "tsc": "tsc",
"start": "live-server --open=src" "tsc:w": "tsc -w",
"lite": "lite-server",
"both": "concurrent \"npm run tsc:w\" \"npm run lite\" "
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
"license": "ISC", "license": "ISC"
"dependencies": {
"angular2": "2.0.0-alpha.46",
"systemjs": "0.19.2"
},
"devDependencies": {
"live-server": "^0.8.1",
"typescript": "^1.6.2"
}
} }

View File

@ -0,0 +1,5 @@
{
"description": "Hierachical Injectors",
"files":["!**/*.d.ts", "!**/*.js"],
"tags": ["dependency", "injection"]
}

View File

@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
}
}

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES5",
"module": "system",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": true,
"suppressImplicitAnyIndexErrors": true
},
"exclude": [
"node_modules"
]
}

View File

@ -1,36 +1,43 @@
include ../../../../_includes/_util-fns include ../../../../_includes/_util-fns
:marked :marked
We learned the basics of Angular Dependency injection in an We learned the basics of Angular Dependency injection in the
[earlier chapter](./dependency-injection.html). [Dependency Injection](./dependency-injection.html) chapter.
In this chapter we learn that Angular has an Angular has an Hierarchical Dependency Injection system.
Hierarchical Dependency Injection system that supports trees of injectors. There is actually a tree of injectors
that parallel an application's component tree.
In practice, there is a tree of injectors that parallel an application's component tree.
We can re-configure the injectors at any level of that component tree with We can re-configure the injectors at any level of that component tree with
interesting and useful results. interesting and useful results.
In this chapter we explore these points and write some code.
[Live Example](/resources/live-examples/hierarchical-dependency-injection/ts/plnkr.html).
.l-main-section .l-main-section
:marked :marked
## The Injector Tree ## The Injector Tree
In an [earlier chapter](./dependency-injection.html) In the [Dependency Injection](./dependency-injection.html) chapter
we learned how to configure a dependency injector in different ways and how to retrieve dependencies where we need them. we learned how to configure a dependency injector and how to retrieve dependencies where we need them.
What if we told you there is no such thing as ***the*** injector? We oversimplified. In fact, there is no such thing as ***the*** injector!
In fact, each application has multiple injectors! An application may have multiple injectors!
An Angular application is a tree of components. Each component instance has its own injector!
The tree of components parallels the tree of injectors.
We may have heard that an Angular application is a tree of components.
It may surprise us to learn that there is a corresponding tree of injectors
and each component instance in that tree has its own injector!
.l-sub-section .l-sub-section
:marked :marked
That isn't literally true. Angular is more efficient than that. What is true is that each component instance Angular doesn't *literally* create a separate injector for each component.
has an injector and that components at different levels in the tree have different injectors. Every component doesn't need its own injector and it would be horribly inefficient to create
masses of injectors for no good purpose.
It is helpful for our purposes to pretend that every component has its own injector. But it is true that every component ***has an injector*** (even if it shares that injector with another component)
and there may be many different injector instances operating at different levels of the component tree.
It is useful to pretend that every component has its own injector.
:marked :marked
Consider a simple variation on the Tour of Heroes application consisting of three different components: Consider a simple variation on the Tour of Heroes application consisting of three different components:
`HeroesApp`, `HeroesListComponent` and `HeroesCardComponent`. `HeroesApp`, `HeroesListComponent` and `HeroesCardComponent`.
@ -41,7 +48,7 @@ include ../../../../_includes/_util-fns
open simultaneously. open simultaneously.
figure.image-display figure.image-display
img(src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree") img(src="/resources/images/devguide/dependency-injection/component-hierarchy.png" alt="injector tree" width="500")
:marked :marked
Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree. Each component instance gets its own injector and an injector at one level is a child injector of the injector above it in the tree.
@ -78,13 +85,13 @@ figure.image-display
`Tires` resolved by the root injector (A). `Tires` resolved by the root injector (A).
figure.image-display figure.image-display
img(src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="injector tree") img(src="/resources/images/devguide/dependency-injection/injector-tree.png" alt="injector tree" width="600")
.l-main-section .l-main-section
:marked :marked
## Component Injectors ## Component Injectors
In the previous section, we talked about injectors and how they are organized like a tree. Lookups follow the injector tree upwards until they found the requested thing to inject. But when do we actually want to provide bindings on the root injector and when do we want to provide them on a child injector? In the previous section, we talked about injectors and how they are organized like a tree. Lookups follow the injector tree upwards until they found the requested thing to inject. But when do we actually want to provide providers on the root injector and when do we want to provide them on a child injector?
Consider you are building a component to show a list of super heroes that displays each super hero in a card with its name and superpower. There should also be an edit button that opens up an editor to change the name and superpower of our hero. Consider you are building a component to show a list of super heroes that displays each super hero in a card with its name and superpower. There should also be an edit button that opens up an editor to change the name and superpower of our hero.
@ -92,64 +99,68 @@ figure.image-display
Lets take a look at the `HeroesListComponent` which is the root component for this example. Lets take a look at the `HeroesListComponent` which is the root component for this example.
+makeExample('hierarchical-dependency-injection/ts/src/app/heroes-list.component.ts') +makeExample('hierarchical-dependency-injection/ts/app/heroes-list.component.ts', null, 'app/heroes-list.component.ts')
:marked :marked
Notice that it imports the `HeroService` that weve used before so we can skip its declaration. The only difference is that weve used a more formal approach for our `Hero`model and defined it upfront as such. Notice that it imports the `HeroService` that weve used before so we can skip its declaration. The only difference is that weve used a more formal approach for our `Hero`model and defined it upfront as such.
+makeExample('hierarchical-dependency-injection/ts/src/app/hero.ts') +makeExample('hierarchical-dependency-injection/ts/app/hero.ts', null, 'app/hero.ts')(format=".")
:marked :marked
Our `HeroesListComponent` defines a template that creates a list of `HeroCardComponents` and `HeroEditorComponents`, each bound to an instance of hero that is returned from the `HeroService`. Ok, thats not entirely true. It actually binds to an `EditItem<Hero>` which is a simple generic datatype that can wrap any type and indicate if the item being wrapped is currently being edited or not. Our `HeroesListComponent` defines a template that creates a list of `HeroCardComponents` and `HeroEditorComponents`, each bound to an instance of hero that is returned from the `HeroService`. Ok, thats not entirely true. It actually binds to an `EditItem<Hero>` which is a simple generic datatype that can wrap any type and indicate if the item being wrapped is currently being edited or not.
+makeExample('hierarchical-dependency-injection/ts/src/app/edit-item.ts') +makeExample('hierarchical-dependency-injection/ts/app/edit-item.ts', null, 'app/edit-item.ts')(format=".")
:marked :marked
But how is `HeroCardComponent` implemented? Lets take a look. But how is `HeroCardComponent` implemented? Lets take a look.
+makeExample('hierarchical-dependency-injection/ts/src/app/hero-card.component.ts') +makeExample('hierarchical-dependency-injection/ts/app/hero-card.component.ts', null, 'app/hero-card.component.ts')
:marked :marked
The `HeroCardComponent` is basically a component that defines a template to render a hero. Nothing more. The `HeroCardComponent` is basically a component that defines a template to render a hero. Nothing more.
Lets get to the interesting part and take a look at the `HeroEditorComponent` Lets get to the interesting part and take a look at the `HeroEditorComponent`
+makeExample('hierarchical-dependency-injection/ts/src/app/hero-editor.component.ts') +makeExample('hierarchical-dependency-injection/ts/app/hero-editor.component.ts', null, 'app/hero-editor.component.ts')
:marked :marked
Now here its getting interesting. The `HeroEditorComponent`defines a template with an input to change the name of the hero and a `cancel` and a `save` button. Remember that we said we want to have the flexibility to cancel our editing and restore the old value? This means we need to maintain two copies of our `Hero` that we want to edit. Thinking ahead this is a perfect use case to abstract it into its own generic service since we have probably more cases like this in our app. Now here its getting interesting. The `HeroEditorComponent`defines a template with an input to change the name of the hero and a `cancel` and a `save` button. Remember that we said we want to have the flexibility to cancel our editing and restore the old value? This means we need to maintain two copies of our `Hero` that we want to edit. Thinking ahead this is a perfect use case to abstract it into its own generic service since we have probably more cases like this in our app.
And this is where the `RestoreService` enters the stage. And this is where the `RestoreService` enters the stage.
+makeExample('hierarchical-dependency-injection/ts/src/app/restore-service.ts') +makeExample('hierarchical-dependency-injection/ts/app/restore.service.ts', null, 'app/estore.service.ts')
:marked :marked
All this tiny service does is define an API to set a value of any type which can be altered, retrieved or set back to its initial value. Thats exactly what we need to implement the desired functionality. All this tiny service does is define an API to set a value of any type which can be altered, retrieved or set back to its initial value. Thats exactly what we need to implement the desired functionality.
Our `HeroEditComponent` uses this services under the hood for its `hero` property. It intercepts the `get` and `set` method to delegate the actual work to our `RestoreService` which in turn makes sure that we wont work on the original item but on a copy instead. Our `HeroEditComponent` uses this services under the hood for its `hero` property. It intercepts the `get` and `set` method to delegate the actual work to our `RestoreService` which in turn makes sure that we wont work on the original item but on a copy instead.
At this point we may be scratching our heads asking what this has to do with component injectors? If we look closely at our `HeroEditComponent` well notice this piece of code At this point we may be scratching our heads asking what this has to do with component injectors?
If closely at the metadata for our `HeroEditComponent`. Notice the `providers` property.
+makeExample('hierarchical-dependency-injection/ts/src/app/hero-editor.component.ts', 'providers')
+makeExample('hierarchical-dependency-injection/ts/app/hero-editor.component.ts', 'providers')
:marked :marked
This creates a binding for the `RestoreService` in the injector of the `HeroEditComponent`. But couldnt we simply alter our bootstrap call to this? This adds a `RestoreService` provider to the injector of the `HeroEditComponent`.
Couldnt we simply alter our bootstrap call to this?
```
bootstrap(HeroesListComponent, [HeroService, RestoreService]);
```
+makeExample('hierarchical-dependency-injection/ts/app/boot.ts', 'bad-alternative')
:marked
Technically we could, but our component wouldnt quite behave the way it is supposed to. Remember that each injector treats the services that it provides as singletons. However, in order to be able to have multiple instances of `HeroEditComponent` edit multiple heroes at the same time we need to have multiple instances of the `RestoreService`. More specifically each instance of `HeroEditComponent` needs to be bound to its own instance of the `RestoreService`. Technically we could, but our component wouldnt quite behave the way it is supposed to. Remember that each injector treats the services that it provides as singletons. However, in order to be able to have multiple instances of `HeroEditComponent` edit multiple heroes at the same time we need to have multiple instances of the `RestoreService`. More specifically each instance of `HeroEditComponent` needs to be bound to its own instance of the `RestoreService`.
By configuring a binding for the `RestoreService` on the `HeroEditComponent`, we get exactly one instance of the `RestoreService`per `HeroEditComponent`. By configuring a provider for the `RestoreService` on the `HeroEditComponent`, we get exactly one new instance of the `RestoreService`per `HeroEditComponent`.
Does that mean that services arent singletons anymore in Angular 2? Yes and no. Does that mean that services arent singletons anymore in Angular 2? Yes and no.
While theres only one instance per binding per injector there may be multiple instances of the same type across There can be only one instance of a service type in a particular injector.
the entire application due to the fact that we can create multiple bindings for the same type on different components. But we've learned that we can have multiple injectors operating at different levels of the application's component tree.
Any of those injectors could have its own instance of the service.
If we had only defined a binding for `RestoreService` on the root component, If we defined a `RestoreService` provider only on the root component,
we would have exactly one instance of the across the entire applicatoin. Thats clearly not what we want in this scenario. we would have exactly one instance of that service and it would be shared across the entire application.
We dont want to share an instance. We want each component to have its own instance of the `RestoreService`.
Thats clearly not what we want in this scenario. We want each component to have its own instance of the `RestoreService`.
Defining (or re-defining) a provider at the component level creates a new instance of the service for each new instance
of that component. We've made the `RestoreService` a kind of "private" singleton for each `HeroEditComponent`,
scoped to that component instance and its child components.
<!-- <!--
## Advanced Dependency Injection in Angular 2 ## Advanced Dependency Injection in Angular 2
@ -162,5 +173,5 @@ figure.image-display
:marked :marked
## Dependency Visibility ## Dependency Visibility
[TODO] (bindings vs viewBindings) This has been postponed for now until come up with a decent use case [TODO] (providers vs viewProviders) This has been postponed for now until come up with a decent use case
--> -->