AngularDart4.0 指南- 依賴注入

依賴注入是一個重要的應用程序設計模式。 它的用途很是普遍,幾乎全部人都稱之爲DI。html

Angular擁有本身的依賴注入框架,若是沒有它,你真的不能構建一個Angular應用程序。java

本頁面涵蓋了DI是什麼,爲何它是有用的,以及如何使用Angular DI。git

運行實例(查看源代碼)。github

爲何使用依賴注入?

要理解爲何依賴注入如此重要,請考慮沒有它的例子。 想象一下寫下面的代碼:web

lib/src/car/car.dart (without DI)bootstrap

class Car {
  Engine engine;
  Tires tires;
  var description = 'No DI';

  Car() {
    engine = new Engine();
    tires = new Tires();
  }

  // Method using the engine and tires
  String drive() => '$description car with '
    '${engine.cylinders} cylinders and '
    '${tires.make} tires.';
}

Car類在其構造函數中建立它須要的全部東西。 有什麼問題? 問題在於Car類是脆弱的,不靈活的,難以測試。設計模式

這輛車須要引擎和輪胎。 Car構造函數並不要求它們,而是從特定的Engine類和Tires類中實例化本身的副本。api

若是Engine類發展而它的構造函數須要一個參數呢? 這將打破汽車類,它會保持中斷,直到你改寫engine = new Engine(theNewParameter)的行。 當你第一次寫「Car」時,Engine構造參數甚至不是一個考慮因素。 即便是如今,你也不可能預料到它們。 可是你必須開始關心,由於當Engine定義改變時,Car類必須改變。 這使得Car變得脆弱。數組

若是你想在你的Car上裝一個不一樣品牌的輪胎怎麼辦? 太糟糕了。 你被鎖定在Tires 班製造的任何品牌上。 這使得Car類不靈活。瀏覽器

如今每輛新車都有本身的引擎。 它不能與其餘車輛共享一個引擎。 雖然這對於汽車發動機是有意義的,可是您確定能夠考慮應該共享的其餘依賴性,例如與製造商服務中心的機載無線鏈接。 Car缺少共享之前爲其餘消費者建立的服務的靈活性。

當你爲Car寫測試的時候,你會隱藏它的依賴關係。 在測試環境中甚至能夠建立一個新的EngineEngine是依賴於什麼的? 這個依賴依賴於什麼? 引擎的新實例是否會對服務器進行異步調用? 你固然不但願在測試過程當中發生這種狀況。

若是汽車在輪胎壓力低的時候應該發出警告信號呢? 若是您在測試過程當中沒法換上低壓輪胎,您如何確認它實際上會閃爍警告?

你沒法控制汽車的隱藏依賴。 當你沒法控制依賴時,一個類變得很難測試。

你如何使汽車更強大,更靈活和可測試?

這太容易了。 將Car構造器更改成具備DI的版本:

lib/src/car/car.dart (excerpt with DI)

final Engine engine;
final Tires tires;
String description = 'DI';
Car(this.engine, this.tires);

lib/src/car/car_no_di.dart (excerpt without DI)

Engine engine;
Tires tires;
var description = 'No DI';
Car() {
  engine = new Engine();
  tires = new Tires();
}

看看發生了什麼? 依賴關係的定義如今在構造函數中。 汽車級別再也不建立引擎或輪胎。 它只是消耗它們。

本示例利用Dart的構造函數語法來同時聲明參數和初始化屬性。

如今,您能夠經過將引擎和輪胎傳遞給構造函數來建立一輛汽車。

// Simple car with 4 cylinders and Flintstone tires.
new Car(new Engine(), new Tires())

多麼酷啊? 發動機和輪胎依賴性的定義與Car類是分離的。 只要符合發動機或輪胎的通常API要求,您就能夠傳入任何類型的發動機或輪胎。

若是有人擴展引擎類,那不是汽車的問題。

汽車的消費者有問題。 消費者必須更新汽車創做代碼,以下所示:

class Engine2 extends Engine {
  Engine2(cylinders) : super.withCylinders(cylinders);
}

Car superCar() =>
  // Super car with 12 cylinders and Flintstone tires.
  new Car(new Engine2(12), new Tires())
  ..description = 'Super';

關鍵是這樣的:Car類不須要改變。 你會盡快處理消費者的問題。

Car類如今更容易測試,由於您徹底控制了它的依賴關係。 您能夠將模擬數據傳遞給在每次測試期間徹底按照您但願他們執行的操做的構造函數:

class MockEngine extends Engine {
  MockEngine() : super.withCylinders(8);
}

class MockTires extends Tires {
  MockTires() { make = 'YokoGoodStone'; }
}

Car testCar() =>
  // Test car with 8 cylinders and YokoGoodStone tires.
  new Car(new MockEngine(), new MockTires())
  ..description = 'Test';

你剛纔知道什麼是依賴注入。

這是一種編碼模式,在這種模式下,類從外部來源得到依賴關係,而不是本身建立它們。

涼! 那麼這個可憐的消費者呢? 任何想要汽車的人如今都必須創造三個部分:汽車,發動機和輪胎。 汽車類消費者花錢解決問題。 你須要一些照顧組裝這些零件的東西。

你能夠寫一個巨人班來作到這一點:lib/src/car/car_factory.dart

import 'car.dart';

// BAD pattern!
class CarFactory {
  Car createCar() =>
      new Car(createEngine(), createTires())
        ..description = 'Factory';

  Engine createEngine() => new Engine();
  Tires createTires() => new Tires();
}

如今用三種建立方法並無那麼糟糕。 可是隨着應用程序的增加,維護它將會變得輕易。 這個工廠將成爲一個相互依賴的工廠方法的巨大蜘蛛網!

若是你能夠簡單地列出你想要構建的東西,而沒必要定義哪些依賴被注入什麼東西,那不是很好嗎?

這是依賴注入框架發揮做用的地方。 想象一下框架有一個叫作注入器的東西。 你用這個注射器註冊一些類,而後找出如何建立它們。

當你須要Car的時候,你只須要讓注射器爲你準備好,你就能夠走了。

var car = injector.get(Car);

每一個人都贏了 汽車對於創造引擎或輪胎一無所知。 消費者對創造汽車一無所知。 你沒有一個龐大的工廠班來維護。 汽車和消費者只需詢問他們須要什麼和傳遞注入器。

這就是依賴注入框架的所有內容。

Angular 依賴注入


Angular 承載有本身的依賴注入框架。 您將經過討論本指南附帶的示例應用程序來學習Angular Dependency Injection。 隨時運行實例(查看源代碼)。

首先從「英雄之旅」回顧英雄特徵的簡化版本。

lib/src/heroes/heroes_component.dart

import 'package:angular/angular.dart';
import 'hero_list_component.dart';
@Component(
    selector: 'my-heroes',
    template: '''
      <h2>Heroes</h2>
      <hero-list></hero-list>''',
    directives: const [HeroListComponent])
class HeroesComponent {}


lib/src/heroes/hero_list_component.dart

import 'package:angular/angular.dart';
import 'hero.dart';
import 'mock_heroes.dart';
@Component(
  selector: 'hero-list',
  template: '''
    <div *ngFor="let hero of heroes">
      {{hero.id}} - {{hero.name}}
    </div>''',
  directives: const [CORE_DIRECTIVES],
)
class HeroListComponent {
  final List<Hero> heroes = mockHeroes;
}


lib/src/heroes/hero.dart

class Hero {
  final int id;
  final String name;
  final bool isSecret;
  Hero(this.id, this.name, [this.isSecret = false]);
}


lib/src/heroes/mock_heroes.dart

import 'hero.dart';
List<Hero> mockHeroes = <Map>[
  {'id': 11, 'isSecret': false, 'name': 'Mr. Nice'},
  {'id': 12, 'isSecret': false, 'name': 'Narco'},
  {'id': 13, 'isSecret': false, 'name': 'Bombasto'},
  {'id': 14, 'isSecret': false, 'name': 'Celeritas'},
  {'id': 15, 'isSecret': false, 'name': 'Magneta'},
  {'id': 16, 'isSecret': false, 'name': 'RubberMan'},
  {'id': 17, 'isSecret': false, 'name': 'Dynama'},
  {'id': 18, 'isSecret': true, 'name': 'Dr IQ'},
  {'id': 19, 'isSecret': true, 'name': 'Magma'},
  {'id': 20, 'isSecret': true, 'name': 'Tornado'}
].map(_initHero).toList();
Hero _initHero(Map heroProperties) => new Hero(
    heroProperties['id'], heroProperties['name'], heroProperties['isSecret']);

HeroesComponent是頂級英雄組件。 它的惟一目的是顯示顯示英雄名字列表的HeroListComponent

HeroListComponent的這個版本從mockHeroes獲取它的英雄,這是一個在單獨文件中定義的內存集合。

lib/src/heroes/hero_list_component.dart (class)

class HeroListComponent {
  final List<Hero> heroes = mockHeroes;
}

這在發展的早期階段可能就足夠了,可是還不夠理想。 只要你嘗試測試這個組件或從遠程服務器獲取英雄,你就必須改變HeroListComponent的實現,並替換mockHeroes數據的每個其餘用途。

建立一個可注入HeroService

最好把關於英雄數據訪問的細節隱藏在本身定義的服務類的文件中。

lib/src/heroes/hero_service.dart

import 'package:angular/angular.dart';

import 'hero.dart';
import 'mock_heroes.dart';

@Injectable()
class HeroService {
  List<Hero> getHeroes() => mockHeroes;
}

如今假定@Injectable()註解是每一個Angular服務定義中的一個重要組成部分。 服務類公開了一個getHeroes()方法,該方法返回與以前相同的模擬數據。

固然,這不是一個真正的數據服務。 若是服務實際上從遠程服務器獲取數據,則getHeroes()方法簽名將是異步的。 英雄和HTTP教程部分介紹了這樣的英雄服務。 這裏的重點是服務注入,因此同步服務就足夠了。

註冊一個服務提供商

一個服務只是Angular中的一個類,直到您使用Angular依賴注入器註冊它。

一個Angular注入器負責建立服務實例並將它們注入類如HeroListComponent。

你不多本身建立一個Angular注入器。 Angular在執行應用程序時爲您建立注入器,從引導過程當中建立的根注入器開始。

在注入器能夠建立該服務以前,您必須向providers註冊注入器。

providers告訴注入器如何建立服務。 沒有providers,注入者不知道它是負責注入服務,也不能建立服務。

您將在下面瞭解更多關於providers的信息。 如今知道他們建立服務而且必須註冊一個注入器就足夠了。

註冊providers的最經常使用方法是使用任何具備providers列表參數的Angular註解。 其中最多見的是@Component

@Component providers

這裏是修改後的HeroesComponent,在其providers列表中註冊HeroService

lib/src/heroes/heroes_component.dart (revised)

import 'package:angular/angular.dart';

import 'hero_list_component.dart';
import 'hero_service.dart';

@Component(
    selector: 'my-heroes',
    template: '''
      <h2>Heroes</h2>
      <hero-list></hero-list>''',
    providers: const [HeroService],
    directives: const [HeroListComponent])
class HeroesComponent {}

HeroService的一個實例如今可用於注入在此HeroesComponent及其全部子組件中。

組件提供的服務具備有限的生命週期。 組件的每一個新實例都會去得到它所包含的服務實例,當組件實例被銷燬時,服務實例也被銷燬。

在這個示例應用程序中,HeroComponent是在應用程序啓動時建立的,而且永遠不會銷燬,所以爲HeroComponent建立的HeroService也依賴於應用程序的生命週期而存在。

Bootstrap providers

另外一種經常使用的註冊提供者的方法是使用bootstrap()函數。

應用程序在web / main.dart中引導:

import 'package:angular/angular.dart';
import 'package:dependency_injection/app_component.dart';

void main() {
  bootstrap(AppComponent);
}

bootstrap()的第一個參數是應用程序根組件類。 第二個可選參數是提供者列表。 例如:

bootstrap(AppComponent,
  [HeroService]); // DISCOURAGED (but works)

HeroService的一個實例如今可用於注入整個應用程序。

Bootstrap程序配置一般將應用程序包外部聲明的服務保留給整個應用程序範圍。這就是爲何不鼓勵使用引導註冊應用程序特定服務的緣由。 首選的方法是在應用組件中註冊應用服務。

因爲HeroService是在Heroes功能集內使用的,而在其餘地方沒法使用HeroService,所以註冊它的理想位置是HeroesComponent

如下是引導程序提供程序的一個更實際的示例,摘自教程,第5部分。它還說明了您將會在本頁後面介紹的更高級的概念。../toh-5/web/main.dart

import 'package:angular/angular.dart';
import 'package:angular_router/angular_router.dart';
import 'package:angular_tour_of_heroes/app_component.dart';

void main() {
  bootstrap(AppComponent, [
    ROUTER_PROVIDERS,
    // Remove next line in production
    provide(LocationStrategy, useClass: HashLocationStrategy),
  ]);
}

注入服務

HeroListComponent應該從HeroService得到heroes 。

該組件不該該使用new建立HeroService。 它應該要求注入HeroService

您能夠經過指定具備依賴類型的構造函數參數來告訴Angular在組件的構造函數中注入依賴項。 這裏是HeroListComponent構造函數,要求注入HeroService

HeroListComponent(HeroService heroService)

固然,HeroListComponent應該對注入的HeroService作些什麼。 這裏是修改後的組件,使用注入的服務,與之前的版本並排比較。

lib/src/heroes/hero_list_component.dart (with DI)

import 'package:angular/angular.dart';
import 'hero.dart';
import 'hero_service.dart';
@Component(
  selector: 'hero-list',
  template: '''
    <div *ngFor="let hero of heroes">
      {{hero.id}} - {{hero.name}}
    </div>''',
  directives: const [CORE_DIRECTIVES],
)
class HeroListComponent {
  final List<Hero> heroes;
  HeroListComponent(HeroService heroService) : heroes = heroService.getHeroes();
}

lib/src/heroes/hero_list_component.dart (without DI)

import 'package:angular/angular.dart';
import 'hero.dart';
import 'mock_heroes.dart';
@Component(
  selector: 'hero-list',
  template: '''
    <div *ngFor="let hero of heroes">
      {{hero.id}} - {{hero.name}}
    </div>''',
  directives: const [CORE_DIRECTIVES],
)
class HeroListComponent {
  final List<Hero> heroes = mockHeroes;
}

注意,HeroListComponent不知道HeroService來自哪裏。 你知道它來自父級的HeroesComponent。 惟一重要的是在某些父注入器中提供HeroService

單實例服務

服務在注入器範圍內是單實例的。 在給定的注射器中最多隻有一個服務實例。

然而,Angular DI是一個分層注入系統,這意味着嵌套的注入器能夠建立本身的服務實例。 Angular始終建立嵌套的注入器。

組件子注入器

例如,當Angular建立一個具備@Component.providers的組件的新實例時,它也爲該實例建立一個新的子注入器。

組件注入器是相互獨立的,每一個組件都建立它本身的組件提供服務的實例。

當Angular銷燬這些組件之一的實例時,它也會銷燬該組件的注入器和注入器的服務實例。

因爲注入器繼承,您仍然能夠將應用程序範圍的服務注入到這些組件中。 組件的注入器是其父組件的注入器的子組件,而且是其父組件的注入器的後代,因此一直回到應用程序的根注入器。 Angular能夠注入由該譜系中的任何注射器提供的服務。

測試組件

早些時候,你看到設計一個依賴注入類使得類更容易測試。 列出依賴做爲構造函數參數多是全部你須要有效地測試應用程序部分。

例如,你可使用模擬服務建立一個新的HeroListComponent,你能夠在測試中操做它:

var expectedHeroes = [new Hero(0, 'A'), new Hero(1, 'B')];
var mockService = new MockHeroService(expectedHeroes);
it('should have heroes when HeroListComponent created', () {
  var hlc = new HeroListComponent(mockService);
  expect(hlc.heroes.length).toEqual(expectedHeroes.length);
});

測試中瞭解更多。

當服務須要服務時

HeroService很是簡單。 它沒有任何本身的依賴關係。

若是它有一個依賴呢? 若是經過日誌記錄服務報告其活動呢? 你會應用相同的構造函數注入模式,添加一個帶有Logger參數的構造函數。

這裏是修改後的HeroService注入Logger,與之前的服務並排比較。

lib/src/heroes/hero_service.dart (v2)

import 'package:angular/angular.dart';
import '../logger_service.dart';
import 'hero.dart';
import 'mock_heroes.dart';
@Injectable()
class HeroService {
  final Logger _logger;
  HeroService(this._logger);
  List<Hero> getHeroes() {
    _logger.log('Getting heroes ...');
    return mockHeroes;
  }
}

lib/src/heroes/hero_service.dart (v1)

import 'package:angular/angular.dart';
import 'hero.dart';
import 'mock_heroes.dart';
@Injectable()
class HeroService {
  List<Hero> getHeroes() => mockHeroes;
}

構造函數要求注入Logger的實例,並將其存儲在一個名爲logger的專用字段中。 getHeroes()方法在被要求獲取英雄時記錄消息。

依賴Logger服務

示例應用程序的Logger服務很是簡單:lib/src/logger_service.dart

import 'package:angular/angular.dart';

@Injectable()
class Logger {
  List<String> _logs = [];
  List<String> get logs => _logs;

  void log(String message) {
    _logs.add(message);
    print(message);
  }
}

一個真正的專業實現可能會使用日誌包

若是應用程序沒有提供這個Logger,Angular會在它尋找一個Logger注入HeroService的時候拋出一個異常。

EXCEPTION: No provider for Logger! (HeroListComponent -> HeroService -> Logger)

因爲單實例日誌服務在應用程序中隨處可見,所以它已在AppComponent中註冊:lib/app_component.dart (excerpt)

providers: const [Logger]

@Injectable()

@Injectable()註解標識一個服務類可用於實例化注入器。 通常來講,當試圖實例化一個沒有標記爲@Injectable()的類時,注入器會報錯。

注入器也負責實例化像HeroesComponent這樣的組件。 爲何不是HeroesComponent標記爲@Injectable()

你能夠添加它,若是你真的想。 這是沒有必要的,由於HeroesComponent已經被標記了@Component,而且這個標註類(像@Directive@Pipe,稍後你會學到)是Injectable的子類型。 事實上,Injectable註釋將類標識爲注入器實例化的目標。

老是包含括號

老是要寫成@Injectable(),而不只僅是@Injectable。 元數據註解必須是對編譯時常量變量的引用,或對Injectable()等常量構造函數的調用。

若是忘記括號,分析器將會抱怨:「註解建立必須有參數」。 若是您嘗試運行應用程序,它將沒法正常工做,控制檯會說「表達式必須是編譯時常量」。

Providers

服務提供者提供依賴性值的具體運行時版本。 注入器依靠提供者建立注入器注入組件,指令,管道和其餘服務的服務實例。

您必須使用注入器註冊服務provider,不然將不知道如何建立服務。

接下來的幾節將解釋你能夠註冊一個提供者的許多方法。

該類做爲本身的提供者

有不少方法能夠提供實現Logger的東西。 記錄器類自己是一個顯而易見的原生提供者。

providers: const [Logger]

但這不是惟一的方法。

您能夠配置一個能夠傳遞Logger的注入器代替供應商,你能夠提供一個替代類。

你能夠給它一個調用一個記錄器工廠函數的提供者,在正確的狀況下,任何這些方法均可能是一個不錯的選擇。

重要的是,注入器有一個提供者,當它須要一個Logger

Provider類

再次,這是Provider類的語法。

providers: const [Logger]

這其實是使用Provider類實例進行提供者註冊的簡寫表達式:

const [const Provider(Logger, useClass: Logger)]

第一個Provider構造函數參數是做爲定位依賴項值和註冊提供者的鍵的標記

第二個是一個命名參數,好比useClass,你能夠把它看做是建立依賴關係值的方法。 有不少方法能夠建立依賴關係值,就像寫許多配方的方法同樣。

替換提供者類

偶爾你會要求不一樣的類提供服務。 如下代碼告訴注入器在有事要求Logger時返回BetterLogger

const [const Provider(Logger, useClass: BetterLogger)]

provide()函數

當在bootstrap()函數中註冊提供者時,可使用provide()函數而不是更詳細的Provider構造函數表達式。 provide()函數接受與Provider構造函數相同的參數。

provide()函數不能用在Angular註解的提供者列表中,由於註釋只能包含const表達式。

具備依賴關係的供給類

也許EvenBetterLogger能夠在日誌消息中顯示用戶名。 此記錄器從注入的UserService獲取用戶,該用戶服務也在應用程序級別注入。

@Injectable()
class EvenBetterLogger extends Logger {
  final UserService _userService;

  EvenBetterLogger(this._userService);

  @override void log(String message) {
    var name = _userService.user.name;
    super.log('Message to $name: $message');
  }
}

配置BetterLogger

const [UserService, const Provider(Logger, useClass: EvenBetterLogger)]

供給類別名

假設一箇舊的組件依賴於一個OldLogger類。 OldLogger具備與NewLogger相同的界面,但因爲某些緣由,您沒法更新舊組件以使用它。

當舊組件使用OldLogger記錄消息時,您須要NewLogger的單例實例來替換它。

當組件要求輸入新的或舊的記錄器時,依賴注入器應該注入該單例實例。 OldLogger應該是NewLogger的別名。

你固然不但願在你的應用程序中使用兩個不一樣的NewLogger實例。 不幸的是,若是你試圖用useClassOldLogger別名到NewLogger,那就只能獲得兩個不一樣的實例。

const [NewLogger,
  // Not aliased! Creates two instances of `NewLogger`
  const Provider(OldLogger, useClass: NewLogger)]

解決方案:使用useExisting選項的別名。

const [NewLogger,
  // Alias OldLogger with reference to NewLogger
  const Provider(OldLogger, useExisting: NewLogger)]

供給值

有時候,提供一個現成的對象,而不是要求注射器從一個類建立它更容易。

class SilentLogger implements Logger {
  @override
  final List<String> logs = const ['Silent logger says "Shhhhh!". Provided via "useValue"'];

  const SilentLogger();

  @override
  void log(String message) { }
}

const silentLogger = const SilentLogger();

而後你使用useValue選項註冊一個供給者,這使得這個對象扮演了記錄器的角色。

const [const Provider(Logger, useValue: silentLogger)]

請參閱非類依賴關係OpaqueToken部分中的更多useValue示例。

工廠提供商

有時基於直到最後一刻你纔得到的信息你須要動態地建立依賴的值。也許信息在瀏覽器會話過程當中反覆改變。

還假設注射服務沒有獨立訪問這些信息的來源。

這種狀況要求工廠提供商

爲了說明這一點,添加一個新的業務需求:HeroService必須隱藏來自普通用戶的祕密英雄。 只有受權用戶才能看到祕密英雄。

EvenBetterLogger同樣,HeroService須要一個關於用戶的真實信息。 它須要知道用戶是否有權查看祕密英雄。 在單個應用程序會話期間,該受權可能會更改,例如您登陸不一樣的用戶。

EvenBetterLogger不一樣,您不能將UserService注入到HeroService中。 HeroService不會直接訪問用戶信息來決定誰被受權,誰不受權。

相反,HeroService構造函數須要一個布爾標誌來控制祕密英雄的顯示。

lib/src/heroes/hero_service.dart (excerpt)

final Logger _logger;
final bool _isAuthorized;

HeroService(this._logger, this._isAuthorized);

List<Hero> getHeroes() {
  var auth = _isAuthorized ? 'authorized' : 'unauthorized';
  _logger.log('Getting heroes for $auth user.');
  return mockHeroes
      .where((hero) => _isAuthorized || !hero.isSecret)
      .toList();
}

您能夠注入Logger,但不能注入布爾isAuthorized。 你必須接管一個工廠提供者建立這個HeroService的新實例。

工廠提供者須要工廠功能:lib/src/heroes/hero_service_provider.dart (excerpt)

HeroService heroServiceFactory(Logger logger, UserService userService) =>
    new HeroService(logger, userService.user.isAuthorized);

儘管HeroService不能訪問UserService,但工廠函數卻能夠。

您將LoggerUserService都注入到工廠提供程序中,讓注入器將它們傳遞給工廠函數:

lib/src/heroes/hero_service_provider.dart (excerpt)

const heroServiceProvider = const Provider<HeroService>(HeroService,
    useFactory: heroServiceFactory,
    deps: const [Logger, UserService]);

useFactory字段告訴Angular提供者是一個工廠函數,其實現是heroServiceFactory

deps屬性是提供者令牌的列表。 LoggerUserService類用做其本身的類提供程序的標記。 注入器解析這些令牌並將相應的服務注入匹配的工廠功能參數。

請注意,您在一個常量,heroServiceProvider中捕獲了工廠提供者。 這額外的步驟使工廠提供者可重用。 你能夠在須要的時候用這個常量註冊HeroService

在這個示例中,只須要在HeroesComponent中,它將替換元數據提供程序數組中的之前的HeroService註冊。 在這裏你能夠看到新的和舊的並行執行:

lib/src/heroes/heroes_component.dart (v3)

import 'package:angular/angular.dart';
import 'hero_list_component.dart';
import 'hero_service_provider.dart';
@Component(
    selector: 'my-heroes',
    template: '''
      <h2>Heroes</h2>
      <hero-list></hero-list>''',
    providers: const [heroServiceProvider],
    directives: const [HeroListComponent])
class HeroesComponent {}

lib/src/heroes/heroes_component.dart (v2)

import 'package:angular/angular.dart';
import 'hero_list_component.dart';
import 'hero_service.dart';
@Component(
    selector: 'my-heroes',
    template: '''
      <h2>Heroes</h2>
      <hero-list></hero-list>''',
    providers: const [HeroService],
    directives: const [HeroListComponent])
class HeroesComponent {}

依賴注入令牌

當您使用注入器註冊提供者時,您將該提供者與依賴注入令牌相關聯。 注入器維護一個內部的令牌提供者映射,當它被要求依賴的時候它會引用它。 令牌是map的key。

在以前的全部例子中,依賴性值都是一個類實例,類類型做爲本身的查找鍵。 在這裏,您經過提供HeroService類型做爲令牌直接從注入器得到HeroService

heroService = _injector.get(HeroService);

當你編寫一個須要注入的基於類的依賴的構造函數時,你也有相似的好運氣。 當您使用HeroService類類型定義構造函數參數時,Angular知道注入與該HeroService類令牌關聯的服務:

HeroListComponent(HeroService heroService)

當您考慮大多數依賴關係值由類提供時,這是特別方便的。

非類依賴關係

若是依賴性值不是一個類呢? 有時你想注入的東西是一個stringlistmap,或者一個function

應用程序一般會定義具備許多小事實(例如應用程序標題或Web API端點地址)的配置對象,但這些配置對象並不老是類的實例。 他們能夠像這樣的地圖文字:

lib/src/app_config.dart (excerpt)

const Map heroDiConfig = const <String,String>{
  'apiEndpoint' : 'api.heroes.com',
  'title' : 'Dependency Injection'
};

若是你想使這個配置對象可用於注入呢? 您知道您能夠向值提供者註冊一個對象。

可是,你應該使用什麼做爲令牌? 你沒有一個類做爲一個令牌; 沒有HeroDiConfig類。

雖然你可使用Map,可是你不該該由於(像StringMap太廣泛。 您的應用程序可能依賴於幾個map,每一個map用於不一樣的目的。

OpaqueToken

爲非類依賴關係選擇提供者令牌的一種解決方案是定義和使用OpaqueToken。 這樣一個令牌的定義以下所示:

import 'package:angular/angular.dart';

const appConfigToken = const OpaqueToken('app.config');

令牌描述是一個開發人員的aid

使用OpaqueToken對象註冊依賴項提供程序:

providers: const [
  const Provider(appConfigToken, useValue: heroDiConfig)]

如今,您可使用@Inject註解幫助將配置對象注入到任何須要它的構造函數中:

AppComponent(@Inject(appConfigToken) Map config) : title = config['title'];

雖然Map接口在依賴注入中不起做用,但它支持在類中輸入配置對象。

自定義配置類

做爲使用配置Map的替代方法,您能夠定義一個自定義配置類:

lib/src/app_config.dart (alternative config)

class AppConfig {
  String apiEndpoint;
  String title;
}

AppConfig heroDiConfigFactory() => new AppConfig()
  ..apiEndpoint = 'api.heroes.com'
  ..title = 'Dependency Injection';

定義一個配置類有幾個好處。 一個關鍵的好處是強大的靜態檢查:若是你拼錯一個屬性名稱或給它分配一個錯誤類型的值,你會被提早警告。 Dart級聯符號(..提供了初始化配置對象的便捷方法。

若是使用級聯,則配置對象不能被聲明爲const,而且不能使用值提供者,但可使用工廠提供者

lib/app_component.dart (providers)

providers: const [
  Logger,
  UserService,
  const Provider(appConfigToken, useFactory: heroDiConfigFactory),
],

lib/app_component.dart (ctor)

AppComponent(@Inject(appConfigToken) AppConfig config, this._userService)
    : title = config.title;

可選的依賴關係

HeroService須要一個Logger,可是若是沒有記錄器能夠獲得呢? 你能夠經過使用@Optional()註解構造函數參數來告訴Angular依賴關係是可選的:

HeroService(@Optional() this._logger) {
  _logger?.log(someMessage);
}

當使用@Optional()時,您的代碼必須考慮空值。 若是您沒有在注入器的某處註冊logger,注入器會將logger的值設置爲空。

概要

你在這個頁面學習了Angular依賴注入的基礎知識。 您能夠註冊各類提供程序,而且您知道如何經過向構造函數添加參數來請求注入的對象(如服務)。

Angular依賴注入比本頁描述的更有能力。 您能夠在層次依賴注入中瞭解更多關於其高級功能的信息,從對嵌套注入器的支持開始。

附錄:直接使用注射器

開發人員不多直接使用注入器,可是這裏有一個InjectorComponent

lib/src/injector_component.dart (injector)

@Component(
  selector: 'my-injectors',
  template: '''
      <h2>Other Injections</h2>
      <div id="car">{{car.drive()}}</div>
      <div id="hero">{{hero.name}}</div>
      <div id="rodent">{{rodent}}</div>''',
  providers: const [
    Car,
    Engine,
    Tires,
    heroServiceProvider,
    Logger,
  ],
)
class InjectorComponent implements OnInit {
  final Injector _injector;
  Car car;
  HeroService heroService;
  Hero hero;

  InjectorComponent(this._injector);

  @override
  void ngOnInit() {
    car = _injector.get(Car);
    heroService = _injector.get(HeroService);
    hero = heroService.getHeroes()[0];
  }

  String get rodent =>
      _injector.get(ROUS, "R.O.U.S.'s? I don't think they exist!");
}

注射器自己是一種注射服務。

在這個例子中,Angular將組件的注入器注入到組件的構造函數中。 該組件而後在ngOnInit()中向注入的注入器詢問它想要的服務。

請注意,服務自己不會被注入到組件中。 他們經過調用injector.get()來檢索。

若是get()方法沒法解析請求的服務,則會引起錯誤。 您可使用第二個參數調用get(),若是未找到該服務,則返回該值。 若是沒有向這個或任何祖先注射器註冊,Angular將沒法找到該服務。

下一節

相關文章
相關標籤/搜索