我们在Dependency Injection已经了解了依赖注入的基本信息。
angular的依赖注入系统是分层的(hierarchical)。它是一个跟组件树并行的injector树。我们可以在组件树的任意一层重新配置injector。
在Dependency Injection我们了解了怎样配置一个injector和获取依赖。实际上应用中有多个injector。Angular应用相当于一个组件树。每个组件实例都有一个injector。injector树跟组件树是并行的。
考虑这种情况:有三个组件,HeroesApp
、HeroesListComponent
和HeroesCardComponent
。HeroesApp
有一个HeroesListComponent
实例,而HeroesListComponent
可能有多个HeroesCardComponent
实例。
每一个组件实例有一个它自己的injector,injector之间的层级关系跟组件树是一致的。
当最底层的组件请求一个依赖时,Angular会试图用组件的provider所注册的自己injector来解决依赖。如果这个组件的injector缺少provider,它就将请求传递到父组件的injector。如果那个injector不能解决依赖,那么它再将请求传递给它的父组件的injector。这样一层一层向上传递请求,知道某一个injector能够解决依赖或者超过了组件树的根组件(Angular会抛出错误)。还有一种可能,中间的组件声明为host组件,那么请求传递到它之后就不再往上传递了。
还是以Car为例。假设我们配置了根组件A,它的providers包含Car
、Engine
和Tires
。创建一个子组件B,定义它的providers为Car
和Engine
。再创建一个组件B的子组件C
,它的providers只有Car
。
当我们从组件C请求Car
实例时,它的injector会创建Car
实例,但是是通过injector B解决Engine
和injector A解决Tires
的。
假设我们要创建一个组件来显示hero列表以及每个hero的详细信息,有个edit按钮来编辑hero的信息。我们希望能同时编辑多个hero,可以同时提交或取消修改。
根组件HeroesListComponent
的代码如下:
// app/heroes-list.component.ts
import {Component} from 'angular2/core';
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 *ngFor="#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;
}
}
// app/hero.ts
export class Hero {
name: string;
power: string;
}
// app/edit-item.ts
export class EditItem<T> {
editing: boolean
constructor (public item: T) {}
}
再看HeroCardComponent
的实现,只是定义了一个模板来显示hero:
// app/hero-card.component.ts
import {Component, Input} from 'angular2/core';
import {Hero} from './hero';
@Component({
selector: 'hero-card',
template: `
<div>
<span>Name:</span>
<span>{{hero.name}}</span>
</div>`
})
export class HeroCardComponent {
@Input() hero: Hero;
}
再看看我们所关注的HeroEditorComponent
的实现:
// app/hero-editor.component.ts
import {Component, Input, Output, EventEmitter} from 'angular2/core';
import {RestoreService} from './restore.service';
import {Hero} from './hero';
@Component({
selector: 'hero-editor',
providers: [RestoreService],
template: `
<div>
<span>Name:</span>
<input [(ngModel)]="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);
}
}
HeroEditorComponent
定义了一个模板,包含更改hero的name的文本框和cancel、save按钮。点击cancel按钮要取消编辑并恢复旧的值,因此我们需要维护两个Hero的副本。我们将它抽象到一个通用的服务RestoreService
中。
// 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));
}
}
注意HeroEditComponent
的providers
属性。我们在HeroEditComponent
的injector提供了RestoreService
:
providers: [RestoreService],
从技术上来说,我们可以在bootstrap中注册:
// Don't do this!
bootstrap(HeroesListComponent, [HeroesService, RestoreService])
但是这样做就会有问题了。记住,每个injector提供的service是单例的。但是,我们需要同时有多个HeroEditComponent
实例来编辑多个hero,同时也需要多个RestoreService
。实际上是每一个HeroEditComponent
实例绑定一个RestoreService
实例。通过在HeroEditComponent
的provider中配置RestoreService
,我们就实现了每一个HeroEditComponent
实例绑定一个RestoreService
实例。
参考资料
Angular2官方文档