在Dependency Injection指南中你学会了基础的Angular依赖注入. Angular有一个层级依赖注入 系统. 实际上是一个与组件树相平行的注入器树. 你可以在组件树的任意层级重新配置注入器. 此指南探索此系统并使用它带来的好处. 尝试live example(view source).
注入器树
在依赖注入指南中, 学会了如何配置依赖注入器和在需要时如何重新获取依赖对象. 事实上,这里没有像注入器这样的东西. 应用程序可能有多个注入器.Angular应用程序是一个组件树.每一个组件实例有它自己的注入器.组件树与注入器树相平行.
组件的注入器可能是组件树中更高层级的祖先注入器的一个代理 . 这是改善效率的具体实现.你不必关心注入器的不同并且你的脑模型应该是每一个组件有它自己的注入器.
思考Tour of Heroes应用程序中指南的变化. 顶层是有若干子组件的AppComponent. 其中一个是HeroesListComponent. HeroesListComponent保留和管理HeroTaxReturnComponent的多个实例. 下面的图表表示当同时打开HeroTaxReturnComponent的三个实例时指南中组件树的第三层的状态 .
注入器冒泡
当一个组件请求依赖时, Angular尝试使用组件自己的注入器中的注册过的提供者满足依赖. 如果组件的注入器没有提供者, 它将向上传递请求到父组件的注入器.如果此组件无法满足请求, 它继续沿着此组件自己的父注入器传递. 此请求保持向上冒泡直到Angular发现一个注入器能处理此请求或在祖先注入器之外运行. 如果它在祖先注入器之外运行, Angular将抛一个错误.
你可以抑制冒泡. 一个媒介组件可以声明它是“host” 组件.此组件将比注入器搜寻提供者更高效.这是以后的主题.
在不同的层级再供给
您可以在注入器树的多个级别重新注册特定依赖性令牌的提供者。 您不必重新注册供应商。 除非你有充分的理由,否则你不应该这样做。但是你可以。 随着解决方案逻辑向上发展,第一个提供商遇到了胜利。 因此,中间注射器中的提供者从树中较低的东西拦截对服务的请求。 它有效地“重新配置”和“隐藏”树中较高级别的提供者。 如果您只指定顶级供应商(通常是根AppComponent),则注入器树看起来是平坦的。 所有请求都会冒泡到您使用bootstrap方法配置的根注入器。
组件注入器
能够在不同级别配置一个或多个提供商开辟了有趣和有用的可能性。
场景:服务
隔离建筑学的思路引导你限制访问应用程序的服务所属的域名.
指南简单引入了显示反叛角色列表的VillainsListComponent. 它从VillainsService中获得反派角色列表.
虽然你可能在根组件AppComponent(就是HeroesService的地方)中提供 VillainsService , 使得VillainsService在应用程序的任何地方都可以获得, 包括Hero工作流.
如果在今后VillainsService发生更改, 你可能需要在hero组件的某个地方中断某些操作. 这不仅发生在想象中以致提供服务的AppComponent将产生风险.
代替方案, 在VillainsListComponent组件元数据providers里提供VillainsService, 例如:
lib/src/villains_list_component.dart (metadata)
代码语言:javascript复制@Component(
selector: 'villains-list',
template: '''
<div>
<h3>Villains</h3>
<ul>
<li *ngFor="let villain of villains | async">{{villain.name}}</li>
</ul>
</div>
''',
directives: const [CORE_DIRECTIVES],
providers: const [VillainsService],
pipes: const [COMMON_PIPES],
)
通过只在VillainsListComponent元数据中提供VillainsService, 服务将仅仅在VillainsListComponent和其子组件树中可用. 它是一个单例,但它是仅在villain域中存在的一个单例. 现在你知道在hero组件中不能使用它.你减少了错误的风险.
场景:多个编辑会话
许多应用程序允许用户同时打开多个任务工作.例如, 在一个预税程序中, 填表人可能操作多个税单,始终由一个值转换到另一个值.
指南在Tour of Heroes主题中以一个简单的例子示范了这个案例. 想象在HeroListComponent之外显示一个超级英雄列表.
打开一个英雄的税单, 填表人单击一个英雄的名字, 打开一个组件编辑收入. 每一个选择的英雄税单都在他自己的组件中打开并且多个返回值能同时被展现 `.
每一个税单都有如下特征:
- 属于它自己的税单编辑会话.
- 能改变一个税单不影响另一个组件的返回值.
- 拥有保存和取消更改税单的能力.
一种可能的假设HeroTaxReturnComponent有管理和恢复更改的逻辑. 那对于一个简单的英雄税单来说是非常棒的.在真实世界中, 使用了详尽的税单数据模型, 编辑将会很棘手. 你可能为管理人员委派一个助手服务, 如此例子所示.
这是HeroTaxReturnService. 它缓存了一个单独的HeroTaxReturn,跟踪返回值的变化, 且能保存和恢复其值. 它也为应用程序范围委派了一个单实例的HeroService, 通过注入获得.
lib/src/hero_tax_return_service.dart
代码语言:javascript复制import 'dart:async';
import 'package:angular/angular.dart';
import 'hero.dart';
import 'heroes_service.dart';
@Injectable()
class HeroTaxReturnService {
final HeroesService _heroService;
HeroTaxReturn _currentTR, _originalTR;
HeroTaxReturnService(this._heroService);
void set taxReturn(HeroTaxReturn htr) {
_originalTR = htr;
_currentTR = new HeroTaxReturn.copy(htr);
}
HeroTaxReturn get taxReturn => _currentTR;
void restoreTaxReturn() {
taxReturn = _originalTR;
}
Future<Null> saveTaxReturn() async {
taxReturn = _currentTR;
await _heroService.saveTaxReturn(_currentTR);
}
}
这是使用它的HeroTaxReturnComponent。
lib/src/hero_tax_return_component.dart
代码语言:javascript复制import 'dart:async';
import 'package:angular/angular.dart';
import 'package:angular_forms/angular_forms.dart';
import 'hero.dart';
import 'hero_tax_return_service.dart';
@Component(
selector: 'hero-tax-return',
template: '''
<div class="tax-return">
<div class="msg" [class.canceled]="message==='Canceled'">{{message}}</div>
<fieldset>
<span id="name">{{taxReturn.name}}</span>
<label id="tid">TID: {{taxReturn.taxId}}</label>
</fieldset>
<fieldset>
<label>
Income: <input type="number" [(ngModel)]="taxReturn.income" class="num">
</label>
</fieldset>
<fieldset>
<label>Tax: {{taxReturn.tax}}</label>
</fieldset>
<fieldset>
<button (click)="onSaved()">Save</button>
<button (click)="onCanceled()">Cancel</button>
<button (click)="onClose()">Close</button>
</fieldset>
</div>
''',
styleUrls: const ['hero_tax_return_component.css'],
directives: const [CORE_DIRECTIVES, formDirectives],
providers: const [HeroTaxReturnService])
class HeroTaxReturnComponent {
final HeroTaxReturnService _heroTaxReturnService;
String message = '';
HeroTaxReturnComponent(this._heroTaxReturnService);
final _close = new StreamController<Null>();
@Output()
Stream<Null> get close => _close.stream;
HeroTaxReturn get taxReturn => _heroTaxReturnService.taxReturn;
@Input()
void set taxReturn(HeroTaxReturn htr) {
_heroTaxReturnService.taxReturn = htr;
}
Future<Null> onCanceled() async {
_heroTaxReturnService.restoreTaxReturn();
await flashMessage('Canceled');
}
void onClose() => _close.add(null);
Future<Null> onSaved() async {
await _heroTaxReturnService.saveTaxReturn();
await flashMessage('Saved');
}
Future<Null> flashMessage(String msg) async {
message = msg;
await new Future.delayed(const Duration(milliseconds: 500));
message = '';
}
}
凭借实现了Getter和Setter方法的输入属性达成tax-return-to-edit . setter使用收入返回值初始化 HeroTaxReturnService的实例. getter始终返回服务中hero的当前状态.组件也向服务发出请求保存和恢复此税单.
这里有一个问题:如果此服务是应用程序范围的单实例.所有组件都需要共享同一个服务实例.每个组件都可能覆盖另一个hero的税单.多么混乱!
观察靠近HeroTaxReturnComponent的元数据.注意 providers 属性.
代码语言:javascript复制providers: const [HeroTaxReturnService])
HeroTaxReturnComponent有它自己的供给器HeroTaxReturnService. 回想每一个组件实例有它自己的注入器.在组件级别提供服务以确保每一个组件获取到它自己的实例, 服务的私有实例.没有税单被覆盖. 不混乱.
方案需要依赖Angular 的其它特性和技术,你可以在文档的其它地方学到. 你可以在live example (view source)预览和下载.
场景景:专业提供商
另一个说法是再供给替代 服务的更多专有实现,在组件树的更深处.
再次思考依赖注入 指南中的例子Car. 建议为CarService, EngineService 和 TiresService用一般的供给器配置根注入器(标记为 A).
创建一个Car组件 (A) 用于显示来自这三个一般服务的汽车的结构汽车的结构.
然后创建一个子组件(B), 定义自己专有的 供给器 CarService 和 EngineService 拥有特殊能力适合在组件(B)中无论发生什么.
组件 (B)是另一个组件 (C)的父组件, 为CarService定义更多特殊的供给器.
此种场景之后,每一个组件建立自己的注入器定义0, 1,或更多供给器 .
当你转变最深层组件(C) Car的实例时, 它的注入器生产一个Car实例通过注入器转变(C) Engine 通过注入器 (B)转变 和 Tires通过根注入器(A)转变.
以下是这款汽车方案的代码:
lib/src/car_components.dart
代码语言:javascript复制import 'package:angular/angular.dart';
import 'car_services.dart';
@Component(
selector: 'c-car',
template: '<div>C: {{description}}</div>',
providers: const [const Provider(CarService, useClass: CarService3)])
class CCarComponent {
String description;
CCarComponent(CarService carService) {
this.description =
'${carService.getCar().description} (${carService.name})';
}
}
@Component(
selector: 'b-car',
template: '''
<div>B: {{description}}</div>
<c-car></c-car>
''',
directives: const [
CCarComponent
],
providers: const [
const Provider(CarService, useClass: CarService2),
const Provider(EngineService, useClass: EngineService2)
])
class BCarComponent {
String description;
BCarComponent(CarService carService) {
this.description =
'${carService.getCar().description} (${carService.name})';
}
}
@Component(
selector: 'a-car',
template: '''
<div>A: {{description}}</div>
<b-car></b-car>
''',
directives: const [BCarComponent])
class ACarComponent {
String description;
ACarComponent(CarService carService) {
this.description =
'${carService.getCar().description} (${carService.name})';
}
}
@Component(
selector: 'my-cars',
template: '''
<h3>Cars</h3>
<a-car></a-car>
''',
directives: const [ACarComponent])
class CarsComponent {}
const carComponents = const [
CarsComponent,
ACarComponent,
BCarComponent,
CCarComponent
];
// generic car-related services
const carServices = const [CarService, EngineService, TiresService];
lib/src/car_services.dart
代码语言:javascript复制import 'package:angular/angular.dart';
/// Model
class Car {
String name = 'Avocado Motors';
Engine engine;
Tires tires;
Car(this.engine, this.tires);
String get description => '$name car with '
'${engine.cylinders} cylinders and '
'${tires.make} tires.';
}
class Engine {
int cylinders = 4;
}
class Tires {
String make = 'Flintstone';
String model = 'Square';
}
//// Engine services ///
@Injectable()
class EngineService {
String id;
EngineService() : id = 'E1';
Engine getEngine() => new Engine();
}
@Injectable()
class EngineService2 extends EngineService {
EngineService2() {
id = 'E2';
}
@override
Engine getEngine() => new Engine()..cylinders = 8;
}
//// Tire services ///
@Injectable()
class TiresService {
final id = 'T1';
Tires getTires() => new Tires();
}
/// Car Services ///
@Injectable()
class CarService {
EngineService engineService;
TiresService tiresService;
String id;
CarService(this.engineService, this.tiresService) : id = 'C1';
Car getCar() => new Car(engineService.getEngine(), tiresService.getTires());
String get name => '$id-${engineService.id}-${tiresService.id}';
}
@Injectable()
class CarService2 extends CarService {
CarService2(EngineService engineService, TiresService tiresService)
: super(engineService, tiresService) {
id = 'C2';
}
@override
Car getCar() => super.getCar()..name = 'BamBam Motors, BroVan 2000';
}
@Injectable()
class CarService3 extends CarService2 {
CarService3(EngineService engineService, TiresService tiresService)
: super(engineService, tiresService) {
id = 'C3';
}
@override
Car getCar() =>
super.getCar()..name = 'Chizzamm Motors, Calico UltraMax Supreme';
}