docs(hierarchical-injectors): use real example code

closes #406

With this change the chapter doesn't use inline
code anymore but instead uses proper runnable
example code.
This commit is contained in:
Christoph Burgdorf 2015-11-25 18:01:44 +01:00 committed by Ward Bell
parent 6be22a0835
commit 4dffc41f3e
12 changed files with 237 additions and 159 deletions

View File

@ -0,0 +1 @@
src/**/*.js

View File

@ -0,0 +1,21 @@
{
"name": "angular2-getting-started",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc -p src -w",
"start": "live-server --open=src"
},
"keywords": [],
"author": "",
"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,6 @@
// #docregion
export class EditItem<T> {
editing: boolean
constructor (public item: T) {}
}
// #docregion

View File

@ -0,0 +1,16 @@
// #docregion
import {Component, Input} from 'angular2/angular2';
import {Hero} from './hero';
@Component({
selector: 'hero-card',
template: `
<div>
<span>Name:</span>
<span>{{hero.name}}</span>
</div>`
})
export class HeroCardComponent {
@Input() hero: Hero;
}
// #docregion

View File

@ -0,0 +1,46 @@
// #docregion
import {Component, Input, Output, EventEmitter} from 'angular2/angular2';
import {RestoreService} from './restore-service';
import {Hero} from './hero';
@Component({
selector: 'hero-editor',
// #docregion providers
providers: [RestoreService],
// #enddocregion providers
template: `
<div>
<span>Name:</span>
<input [(ng-model)]="hero.name"/>
<div>
<button (click)="onSaved()">save</button>
<button (click)="onCanceled()">cancel</button>
</div>
</div>`
})
export class HeroEditorComponent {
@Output() canceled = new EventEmitter();
@Output() saved = new EventEmitter();
constructor(private restoreService: RestoreService<Hero>) {}
@Input()
set hero (hero: Hero) {
this.restoreService.setItem(hero);
}
get hero () {
return this.restoreService.getItem();
}
onSaved () {
this.saved.next(this.restoreService.getItem());
}
onCanceled () {
this.hero = this.restoreService.restoreItem();
this.canceled.next(this.hero);
}
}
// #enddocregion

View File

@ -0,0 +1,6 @@
// #docregion
export class Hero {
name: string;
power: string;
}
// #enddocregion

View File

@ -0,0 +1,53 @@
// #docregion
import {Component, bootstrap} from 'angular2/angular2';
import {EditItem} from './edit-item';
import {HeroesService} from './heroes-service';
import {HeroCardComponent} from './hero-card.component';
import {HeroEditorComponent} from './hero-editor.component';
import {Hero} from './hero';
@Component({
selector: 'heroes-list',
template: `
<div>
<ul>
<li *ng-for="#editItem of heroes">
<hero-card
[hidden]="editItem.editing"
[hero]="editItem.item">
</hero-card>
<button
[hidden]="editItem.editing"
(click)="editItem.editing = true">
edit
</button>
<hero-editor
(saved)="onSaved(editItem, $event)"
(canceled)="onCanceled(editItem)"
[hidden]="!editItem.editing"
[hero]="editItem.item">
</hero-editor>
</li>
</ul>
</div>`,
directives: [HeroCardComponent, HeroEditorComponent]
})
export class HeroesListComponent {
heroes: Array<EditItem<Hero>>;
constructor(heroesService: HeroesService) {
this.heroes = heroesService.getHeroes()
.map(item => new EditItem(item));
}
onSaved (editItem: EditItem<Hero>, updatedHero: Hero) {
editItem.item = updatedHero;
editItem.editing = false;
}
onCanceled (editItem: EditItem<Hero>) {
editItem.editing = false;
}
}
bootstrap(HeroesListComponent, [HeroesService]);
// #enddocregion

View File

@ -0,0 +1,12 @@
import {Hero} from './hero';
export class HeroesService {
heroes: Array<Hero> = [
{ name: "RubberMan", power: 'flexibility'},
{ name: "Tornado", power: 'Weather changer'}
];
getHeroes () {
return this.heroes;
}
}

View File

@ -0,0 +1,25 @@
// #docregion
export class RestoreService<T> {
originalItem: T;
currentItem: T;
setItem (item: T) {
this.originalItem = item;
this.currentItem = this.clone(item);
}
getItem () :T {
return this.currentItem;
}
restoreItem () :T {
this.currentItem = this.originalItem;
return this.getItem();
}
clone (item: T) :T {
// super poor clone implementation
return JSON.parse(JSON.stringify(item));
}
}
// #enddocregion

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<title>Hierarchical Injectors</title>
<script src="../node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
<script>
System.config({
packages: {'app': {defaultExtension: 'js'}}
});
System.import('app/heroes-list.component');
</script>
</head>
<body>
<!-- #docregion heroes-list -->
<heroes-list>loading...</heroes-list>
<!-- #enddocregion heroes-list -->
</body>
</html>

View File

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

View File

@ -1,3 +1,4 @@
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 an
@ -91,189 +92,47 @@ 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')
import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
import {HeroService} from './hero.service';
import {HeroCardComponent} from './hero-card.component';
import {HeroEditorComponent} from './hero-editor.component';
import {Hero} from './hero';
@Component({
selector: 'heroes-list-component',
template: `
<div>
<ul>
<li *ng-for="#editItem of heroes">
<hero-card-component
[hidden]="editItem.editing"
[hero]="editItem.item">
</hero-card-component>
<button
[hidden]="editItem.editing"
(click)="editItem.editing = true">
edit
</button>
<hero-editor-component
(saved)="onSaved(editItem, $event)"
(canceled)="onCanceled(editItem)"
[hidden]="!editItem.editing"
[hero]="editItem.item">
</hero-editor-component>
</li>
</ul>
</div>`,
directives: [CORE_DIRECTIVES, HeroCardComponent, HeroEditorComponent]
})
export class HeroesListComponent {
heroes: Array<Hero>;
constructor(HeroService: HeroService) {
this.heroes = HeroService.getHeroes()
.map(item => new EditItem(item));
}
onSaved (editItem: EditItem<Hero>, updatedHero: Hero) {
editItem.item = updatedHero;
editItem.editing = false;
}
onCanceled (editItem: EditItem<Hero>) {
editItem.editing = false;
}
}
class EditItem<T> {
item: T;
editing: boolean
constructor (public item T) {}
}
bootstrap(HeroesListComponent, [HeroService]);
```
: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')
export class Hero {
name: string;
power: string;
}
```
: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')
: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')
import {Component, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
import {Hero} from './hero';
@Component({
selector: 'hero-card.component',
properties: ['hero'],
template: `
<div>
<span>Name:</span>
<span>{{hero.name}}</span>
</div>`,
directives: [CORE_DIRECTIVES]
})
export class HeroCardComponent {
hero: Hero;
}
```
: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 `HeroEditComponent` Lets get to the interesting part and take a look at the `HeroEditorComponent`
``` +makeExample('hierarchical-dependency-injection/ts/src/app/hero-editor.component.ts')
import {Component, FORM_DIRECTIVES, EventEmitter, bootstrap, CORE_DIRECTIVES} from 'angular2/angular2';
import {RestoreService} from './restore.service';
import {Hero} from './hero';
@Component({ :marked
selector: 'hero-editor-component', 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.
events: ['canceled', 'saved'],
properties: ['hero'],
providers: [RestoreService],
template: `
<div>
<span>Name:</span>
<input [(ng-model)]="hero.name"/>
<div>
<button (click)="onSaved()">save</button>
<button (click)="onCanceled()">cancel</button>
</div>
</div>`,
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
})
export class HeroEditorComponent {
canceled = new EventEmitter();
saved = new EventEmitter();
constructor(private restoreService: RestoreService<Hero>) {}
set hero (hero: Hero) {
this.restoreService.setItem(hero);
}
get hero () {
return this.restoreService.getItem();
}
onSaved () {
this.saved.next(this.restoreService.getItem());
}
onCanceled () {
this.hero = this.restoreService.restoreItem();
this.canceled.next(this.hero);
}
}
```
Now here its getting interesting. The `HeroEditComponent`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')
export class RestoreService<T> {
originalItem: T;
currentItem: T;
setItem (item: T) {
this.originalItem = item;
this.currentItem = this.clone(item);
}
getItem () :T {
return this.currentItem;
}
restoreItem () :T {
this.currentItem = this.originalItem;
return this.getItem();
}
clone (item: T) :T {
// super poor clone implementation
return JSON.parse(JSON.stringify(item));
}
}
```
: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 we look closely at our `HeroEditComponent` well notice this piece of code
``` +makeExample('hierarchical-dependency-injection/ts/src/app/hero-editor.component.ts', 'providers')
providers: [RestoreService]
```
: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 creates a binding for the `RestoreService` in the injector of the `HeroEditComponent`. But couldnt we simply alter our bootstrap call to this?
``` ```