AngularDart4.0 高級-層級依賴注入器

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, EngineServiceTiresService用通常的供給器配置根注入器(標記爲 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';
}
相關文章
相關標籤/搜索