[Angular2] Hierarchical Dependency Injector

我们在Dependency Injection已经了解了依赖注入的基本信息。

angular的依赖注入系统是分层的(hierarchical)。它是一个跟组件树并行的injector树。我们可以在组件树的任意一层重新配置injector。

Injector Tree

在Dependency Injection我们了解了怎样配置一个injector和获取依赖。实际上应用中有多个injector。Angular应用相当于一个组件树。每个组件实例都有一个injector。injector树跟组件树是并行的。

考虑这种情况:有三个组件,HeroesAppHeroesListComponentHeroesCardComponentHeroesApp有一个HeroesListComponent实例,而HeroesListComponent可能有多个HeroesCardComponent实例。

[Angular2] Hierarchical Dependency Injector_第1张图片

每一个组件实例有一个它自己的injector,injector之间的层级关系跟组件树是一致的。

当最底层的组件请求一个依赖时,Angular会试图用组件的provider所注册的自己injector来解决依赖。如果这个组件的injector缺少provider,它就将请求传递到父组件的injector。如果那个injector不能解决依赖,那么它再将请求传递给它的父组件的injector。这样一层一层向上传递请求,知道某一个injector能够解决依赖或者超过了组件树的根组件(Angular会抛出错误)。还有一种可能,中间的组件声明为host组件,那么请求传递到它之后就不再往上传递了。

还是以Car为例。假设我们配置了根组件A,它的providers包含CarEngineTires。创建一个子组件B,定义它的providers为CarEngine。再创建一个组件B的子组件C
,它的providers只有Car

当我们从组件C请求Car实例时,它的injector会创建Car实例,但是是通过injector B解决Engine和injector A解决Tires的。

[Angular2] Hierarchical Dependency Injector_第2张图片

Component Injector

假设我们要创建一个组件来显示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));
  }
}

注意HeroEditComponentproviders属性。我们在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官方文档

你可能感兴趣的:([Angular2] Hierarchical Dependency Injector)