在Dependency Injection指南中你学会了基础的Angular依赖注入.
Angular有一个层级依赖注入 系统. 其实是一个与组件树相平行的注入器树. 你能够在组件树的任意层级从新配置注入器.
此指南探索此系统并使用它带来的好处.
尝试live example(view source).css
在依赖注入指南中, 学会了如何配置依赖注入器和在须要时如何从新获取依赖对象.
事实上,这里没有像注入器这样的东西. 应用程序可能有多个注入器.Angular应用程序是一个组件树.每个组件实例有它本身的注入器.组件树与注入器树相平行.html
组件的注入器多是组件树中更高层级的祖先注入器的一个代理 . 这是改善效率的具体实现.你没必要关心注入器的不一样而且你的脑模型应该是每个组件有它本身的注入器.java
思考Tour of Heroes应用程序中指南的变化. 顶层是有若干子组件的AppComponent. 其中一个是HeroesListComponent. HeroesListComponent保留和管理HeroTaxReturnComponent的多个实例. 下面的图表表示当同时打开HeroTaxReturnComponent的三个实例时指南中组件树的第三层的状态 .git
当一个组件请求依赖时, Angular尝试使用组件本身的注入器中的注册过的提供者知足依赖. 若是组件的注入器没有提供者, 它将向上传递请求到父组件的注入器.若是此组件没法知足请求, 它继续沿着此组件本身的父注入器传递. 此请求保持向上冒泡直到Angular发现一个注入器能处理此请求或在祖先注入器以外运行. 若是它在祖先注入器以外运行, Angular将抛一个错误.github
你能够抑制冒泡. 一个媒介组件能够声明它是“host” 组件.此组件将比注入器搜寻提供者更高效.这是之后的主题.web
您能够在注入器树的多个级别从新注册特定依赖性令牌的提供者。 您没必要从新注册供应商。 除非你有充分的理由,不然你不该该这样作。可是你能够。
随着解决方案逻辑向上发展,第一个提供商遇到了胜利。 所以,中间注射器中的提供者从树中较低的东西拦截对服务的请求。 它有效地“从新配置”和“隐藏”树中较高级别的提供者。
若是您只指定顶级供应商(一般是根AppComponent),则注入器树看起来是平坦的。 全部请求都会冒泡到您使用bootstrap方法配置的根注入器。bootstrap
可以在不一样级别配置一个或多个提供商开辟了有趣和有用的可能性。缓存
隔离建筑学的思路引导你限制访问应用程序的服务所属的域名.async
指南简单引入了显示反叛角色列表的VillainsListComponent. 它从VillainsService中得到反派角色列表.ide
虽然你可能在根组件AppComponent(就是HeroesService的地方)中提供 VillainsService , 使得VillainsService在应用程序的任何地方均可以得到, 包括Hero工做流.
若是在从此VillainsService发生更改, 你可能须要在hero组件的某个地方中断某些操做. 这不只发生在想象中以至提供服务的AppComponent将产生风险.
代替方案, 在VillainsListComponent组件元数据providers里提供VillainsService, 例如:
lib/src/villains_list_component.dart (metadata)
@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
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
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 属性.
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
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
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'; }