依赖注入

版本:Angular 5.0.0-alpha

依赖注入是重要的应用设计模式。它使用得非常广泛,以至于几乎每个人都称它为 DI

Angular 有它自己的依赖注入框架,离开它,你几乎没办法构建出 Angular 应用。

本章将学习什么是 DI,它有什么用,以及如何使用 Angular DI。

运行在线示例 (源代码)。

为什么需要依赖注入?

要理解为什么依赖注入这么重要,不妨先考虑一个不使用它的例子。想象如下代码:

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

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) 和一些轮胎 (tire),它没有去请求现成的实例,而是在构造函数中用具体的EngineTires类实例化出自己的副本。

如果Engine类升级了,它的构造函数要求一个参数,这该怎么办?这个Car类就被破坏了,在把创建引擎的代码重写为engine = new Engine(theNewParameter)之前,它都是坏的。当第一次写Car类时,我们不关心Engine构造函数的参数。现在也不想关心。但是,当Engine类的定义发生变化时,就不得不在乎了,Car类也不得不跟着改变。这就会让Car类过于脆弱。

如果想在Car上使用不同品牌的轮胎会怎样?太糟了。我们被锁定在Tires类创建时使用的那个品牌上。这让Car类缺乏弹性。

现在,每辆新车都有它自己的引擎。它不能和其它车辆共享引擎。虽然这对于汽车来说还算可以理解,但是设想一下那些应该被共享的依赖,比如用来联系厂家服务中心的车载无线电。我们的car缺乏必要的弹性,无法共享当初给其它消费者创建的车载无线电。

当给Car类写测试的时候,你就会受制于它隐藏的那些依赖。能在测试环境中成功创建新的Engine吗?Engine自己又依赖什么?那些依赖本身又依赖什么?Engine的新实例会发起到服务器的异步调用吗?我们当然不想在测试期间这么一层层追下去。

如果Car应该在轮胎气压低的时候闪动警示灯该怎么办?如果没法在测试期间换上一个低气压的轮胎,那该如何确认它能正确的闪警示灯?

我们没法控制这辆车背后隐藏的依赖。当不能控制依赖时,类就会变得难以测试。

该如何让Car更健壮、有弹性以及易于测试?

答案超级简单。把Car的构造函数改造成使用 DI 的版本:

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

发生了什么?我们把依赖的定义移到了构造函数中。Car类不再创建引擎或者轮胎。它仅仅“消费”它们。

这个例子充分利用了 Dart 的构造函数语法来同时声明参数和初始化属性。

现在,通过往构造函数中传入引擎和轮胎来创建一辆车。

// Simple car with 4 cylinders and Flintstone tires.

new Car(new Engine(), new Tires())

这太酷了,不是吗?引擎和轮胎这两个依赖的定义与Car类本身解耦了。只要喜欢,可以传入任何类型的引擎或轮胎,只要它们能满足引擎或轮胎的通用 API 需求。

如果有人扩展了Engine类,那就不再是Car类的问题了。

Car的消费者(即 car 类的实例)才有这个问题。消费者必须更新创建这辆车的代码,就像这样:

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';

刚刚学习了什么是依赖注入

它是一种编程模式,可以让类从外部源中获得它的依赖,而不必亲自创建它们。

酷!但是,可怜的消费者怎么办?那些希望得到一个Car的人们现在必须创建所有这三部分了:CarEngineTiresCar类把它的快乐建立在了消费者的痛苦之上。需要某种机制为我们把这三个部分装配好。

可以写一个巨大的类来做这件事:

// 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();
}

现在只需要三个创建方法,这还不算太坏。但是当应用规模变大之后,维护它将变得惊险重重。这个工厂类将变成由相互依赖的工厂方法构成的巨型蜘蛛网。

如果能简单的列出想建造的东西,而不用定义该把哪些依赖注入到哪些对象中,那该多好!

到了依赖注入框架一展身手的时候了。想象框架中有一个叫做注入器(injector)的东西。用这个注入器注册一些类,它会解决如何创建它们。

当需要一个Car时,就简单的请求注入器获取它就可以了。

var car = injector.get(Car);

皆大欢喜。Car不需要知道如何创建EngineTires。消费者不需要知道如何创建Car。开发人员不需要维护巨大的工厂类。Car和消费者只要简单地请求想要什么,注入器就会交付它们。

这就是关于依赖注入框架的全部。

Angular 依赖注入

Angular 搭载了自己的依赖注入框架。在这篇指南中,你将会通过对一个范例应用的讨论来学习 Angular 的依赖注入技术。运行在线示例 (查看源码)。

先从英雄指南英雄 特性的一个简化版本开始。

HeroesComponent是位于顶级的英雄组件。它唯一的用途是显示 HeroListComponent,而HeroListComponent用于显示一列英雄。

这个版本的HeroListComponentmockHeroes(一个定义在独立文件中的内存集合)中获取 heroes。

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

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

在开发的早期阶段,这就够用了,不过还不是很理想。一旦你试图测试这个组件或者要从远端服务器获取英雄数据时,你就不得不去修改 HeroesListComponent的实现,并要替换所有使用了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> getAll() => mockHeroes;
}

目前先把 @Injectable() 注解当成定义每个 Angular 服务的必备要素。这个服务类暴露一个返回和以前一样的模拟数据的getAll()方法。

当然,这还不是真正的数据服务。如果该应用真的从远端服务器获取数据,那么getAll()的方法签名就应该是异步的。这样的英雄服务是在教程的 HTTP 章节介绍的。这里的重点是服务注入,因此同步服务就足够了。

注册服务提供器

服务仅仅是一个类,直到你使用 Angular 的依赖注入器注册它。

Angular 的依赖注入器负责创建服务的实例,并把它们注入到像HeroListComponent这样的类中。

Angular 当它执行应用时会为你创建大多数注入器,从(可选的)根注入器开始,即你提供给 runApp() 函数的参数。

在注入器能够创建服务之前,你必须使用注入器注册提供器

提供器会告诉注入器如何创建该服务。如果没有提供器,注入器既不知道它该负责创建该服务,也不知道如何创建该服务。

你会在稍后的部分学到更多关于提供器的知识。现在,只要知道它们用于创建服务,以及它们必须用注入器进行注册就行了。

注册提供器最常用的方式是使用任意一个有providers列表参数的 Angular 注解。最常见的注解就是 @Component()

@Component providers

下面是修改过的HeroesComponent,把HeroService注册到了它的providers列表中。

// 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 ClassProvider(HeroService)],
    directives: [HeroListComponent])
class HeroesComponent {}

HeroService的实例现在可以在HeroesComponent及其所有的子组件中注入。

由组件提供的服务,生命周期是有限的。组件的每个新实例都会有它自己的服务实例,当组件实例被销毁时,服务的实例也同样会被销毁。

在这个示例应用中,HeroesComponent会在应用启动时创建,并且它从未销毁,因此,为HeroesComponent创建的HeroService也同样存活在应用的整个生命周期中。

根注入器 providers

你也可以在应用的根注入器注册提供器,即传递给 runApp() 函数的参数。

应用在web/main.dart启动:

// web/main.dart

@GenerateInjector([
  // For illustration purposes only (don't register app-local services here).
  const ClassProvider(HeroService),
])
final InjectorFactory rootInjector = self.rootInjector$Injector;

void main() {
  runApp(ng.AppComponentNgFactory, createInjector: rootInjector);
}

HeroService的实例可以在整个应用程序中注入。

使用根注入器是供外部应用程序包声明全应用范围的服务的。这就是为什么注册应用程序特定的服务是不推荐的。

首选的方法是在应用组件中注册应用服务。因为HeroService被用在Heroes 专题里,没有其它地方了,所以它理想的注册地方是在HeroesComponent内。

下面是一个更真实的根注入器的例子,它来自教程的第五部分

// ../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.template.dart' as ng;

import 'main.template.dart' as self;

@GenerateInjector(
  routerProvidersHash, // You can use routerProviders in production
)
final InjectorFactory injector = self.injector$Injector;

void main() {
  runApp(ng.AppComponentNgFactory, createInjector: injector);
}

注入服务

HeroListComponent应该从HeroService中获取英雄。

该组件不应该使用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: [coreDirectives],
)
class HeroListComponent {
  final List<Hero> heroes;
  HeroListComponent(HeroService heroService) : heroes = heroService.getAll();
}

注意,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> getAll() {
    _logger.fine('Getting heroes ...');
    return mockHeroes;
  }
}

这个构造函数要求注入一个Logger的实例,并把它存到名为_logger的私有属性中。当请求英雄数据时,getAll()方法就会记录一个消息。

被依赖的 Logger 服务

这个示例应用的Logger服务非常简单:

// lib/src/logger_service.dart

import 'package:angular/angular.dart';

@Injectable()
/// Logger that keeps only the last log entry.
class Logger {
  String _log = '';

  void fine(String msg) => _log = msg;

  @override
  String toString() => '[$runtimeType] $_log';
}

一个真实的实现可能需要使用 logging 包

如果该应用没有提供这个Logger服务,当 Angular 试图把Logger注入到HeroService中时,就会抛出一个异常。

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

由于单例的 logger 服务在应用中随处可用,所以要在AppComponent中注册它。

// lib/app_component.dart (excerpt)

providers: [
  const ClassProvider(Logger),
],

@Injectable()

@Injectable()注解标识一个服务类可以被注入器实例化。通常来讲,当试图实例化一个没有被标识为@Injectable()的类时,注入器会报错。

注入器同时负责实例化像HerosComponent这样的组件。为什么HerosComponent没有被标记为@Injectable()呢?

如果你真的想的话,可以添加它。但是没有必要,因为HeroesComponent已经用@Component标记了,这个注解类(和随后将会学到的@Directive@Pipe一样)是 Injectable 的子类型。实际上,正是这些Injectable注解把一个类标识为注入器实例化的目标。

总是带着括号()

总是写@Injectable(),而不仅仅是@Injectable。一个元数据注解必须是一个编译时常量的引用或一个常量构造函数的调用比如Injectable()

如果忘了括号,分析器会显示:"Annotation creation must have arguments"。如果你不管如何都要试图运行这个应用,它不会工作,并且会在控制台显示:"expression must be a compile-time constant"。

提供器

一个服务提供器提供一个具体的、与依赖令牌(token)相关联的运行时实例。注入器根据 提供器 创建可以注入到组件、管道和其它服务的服务的实例。

你必须使用注入器注册一个服务的提供器,否则它不知道如何创建服务。

接下来的几个部分会说明注册一个提供器的多种方法。

类提供器

实现Logger类的方法有很多。最常见的方法是使用 ClassProvider

providers: [
  const ClassProvider(Logger),
],

但这不是唯一的方法。

你可以使用另类可以实现一个Logger的提供器来配置注入器。你可以提供一个替代的类。你可以给它一个调用一个 logger 工厂函数的提供器。在适当的情况下,这些方法中的任何一个都可能是个不错的选择。

重要的是,当注入器需要一个Logger时,它得先有一个提供器。

useClass 提供器

偶尔你会请求一个不同的类来提供服务。下面的代码告诉注入器,当有人请求Logger时,返回一个BetterLogger

const ClassProvider(Logger, useClass: BetterLogger),

带依赖的类提供器

假设EvenBetterLogger可以在日志消息中显示用户名。

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

  EvenBetterLogger(this._userService);

  String toString() => super.toString() + ' (user:${_userService.user.name})';
}

这个日志服务从注入的UserService中取得用户,它也在 app component 的providers的列表中列出。

const ClassProvider(UserService),
const ClassProvider(Logger, useClass: EvenBetterLogger),

现有的提供器

假设一个旧组件依赖一个OldLogger类。OldLoggerNewLogger具有相同的接口,但是由于某些原因,我们不能更新这个旧组件来使用它。

当旧组件使用OldLogger记录消息时,你希望改用NewLogger的单例实例来处理它。

当一个组件请求新的或是旧 logger 时,依赖注入应该注入那个单例实例。OldLogger应该是NewLogger的别名。

你当然不希望应用中有两个不同的NewLogger实例。不幸的是,如果你尝试useClass就会导致这样的后果。

const ClassProvider(NewLogger),
const ClassProvider(OldLogger, useClass: NewLogger),

使用 ExistingProvider 以确保OldLoggerNewLogger提供的是同一个NewLogger实例。

const ClassProvider(NewLogger),
const ExistingProvider(OldLogger, NewLogger),

值提供器

有时,提供一个已准备好的对象比请求注入器从一个类中创建它更容易。

class SilentLogger implements Logger {
  const SilentLogger();
  @override
  void fine(String msg) {}
  @override
  String toString() => '';
}

const silentLogger = const SilentLogger();

然后使用 ValueProvider 来注册这个对象。

const ValueProvider(Logger, silentLogger),

更多ValueProvider的例子,请看:OpaqueToken

工厂提供器

有时,我们需要动态创建这个依赖值,因为它所需要的信息直到最后一刻才能确定。也许这个信息会在浏览器的会话中不停地变化。

假设这个可注入的服务不能独立访问信息源。

这种情况下需要一个工厂提供器

为了说明这一点,添加一个新的业务需求: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> getAll() {
  var auth = _isAuthorized ? 'authorized' : 'unauthorized';
  _logger.fine('Getting heroes for $auth user.');
  return mockHeroes
      .where((hero) => _isAuthorized || !hero.isSecret)
      .toList();
}

你可以注入Logger,但是不能注入布尔型的isAuthorized。你不得不通过工厂提供器创建这个HeroService的新实例。

工厂提供器需要一个工厂方法:

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

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

虽然HeroService不能访问UserService,但是工厂方法可以。

同时把LoggerUserService注入到工厂提供器中,并且让注入器通过工厂函数传递它们:

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

const heroServiceProvider =
    const FactoryProvider(HeroService, heroServiceFactory);

注意,你把这个工厂提供器赋值给了一个常量——heroServiceProvider。这个额外的步骤让工厂提供器可被复用。你可以在任何需要它的地方使用这个常量注册HeroService

在这个例子中,你只在HeroesComponent中需要它,这里,它代替了之前在元数据providers列表中注册的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: [heroServiceProvider],
    directives: [HeroListComponent])
class HeroesComponent {}

令牌(Tokens)

当你使用一个注入器注册提供器时,实际上是使用一个依赖注入令牌把它们关联起来了。注入器维护一个从令牌到提供器的内部映射,当请求一个依赖时它作为参考。

类类型

在前面的所有例子中,令牌是一个类类型,提供的值是这个类型的实例。例如,通过提供HeroService类型作为令牌,从注入器直接获取一个HeroService实例:

heroService = _injector.get(HeroService);

同样的,当你定义一个HeroService类型的构造函数参数时,Angular 就会知道注入一个HeroService实例:

HeroListComponent(HeroService heroService)

OpaqueToken

有时候想要注入的东西是一个字符串、列表、映射乃至一个函数。例如,假使你想要注入这个应用标题呢?

const appTitle = 'Dependency Injection';

你知道值提供器适合于本例,但是你使用什么来作为令牌呢?你可以使用String,但是如果你的应用依赖几个这样的注入的字符串,它并不起作用。

一个解决方案是定义并使用一个 OpaqueToken

import 'package:angular/angular.dart';

const appTitleToken = const OpaqueToken<String>('app.title');

泛型类型参数,虽然是可选的,向开发人员和工具传达了依赖的类型(不要搞混了OpaqueToken构造函数参数的类型,它一直是String)。OpaqueToken参数令牌描述是一个开发人员的帮助。

使用OpaqueToken对象注册依赖提供器:

const ValueProvider.forToken(appTitleToken, appTitle)

现在,在 @Inject() 注解的帮助下,你可以把标题注入到任何需要它的构造函数中:

AppComponent(@Inject(appTitleToken) this.title);

另外,你也可以直接使用OpaqueToken常量作为一个注解:

AppComponent(@appTitleToken this.title);

除了字符串,你还可以注入值。例如,应用有时有一个包含大量简单属性的 Map 类型的配置对象:

const appConfigMap = const {
  'apiEndpoint': 'api.heroes.com',
  'title': 'Dependency Injection',
  // ...
};

const appConfigMapToken = const OpaqueToken<Map>('app.config');

自定义配置类

除了使用一个 Map 作为应用的配置对象,考虑定义一个自定义的应用配置类:

// lib/src/app_config.dart (AppConfig)

class AppConfig {
  String apiEndpoint;
  String title;
}

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

定义一个配置类有几个好处。一个关键的好处是强大的静态检查:如果你拼错了属性名或分配了一个错误类型的值,你会提早被警告。Dart 的级联符号(..)提供了一个初始化配置对象的方便方法。

如果使用级联符号,配置对象不能被声明为const,所有你不能使用值提供器,但你可以使用工厂提供器

// lib/app_component.dart (FactoryProvider)

const FactoryProvider(AppConfig, appConfigFactory),

你可以像下面这样使用这个应用配置:

// lib/app_component.dart (AppComponent)

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

可选依赖

HeroService需要一个Logger,但是怎么在没有 logger 的情况下也能获取它呢?你可以在构造函数的参数中使用 @Optional() 注解,来告诉 Angular 这个依赖是可选的。

HeroService(@Optional() Logger logger) {
  logger?.fine('Hello');
}

当使用@Optional()时,你的代码必须准备好处理一个空值。如果在其它的代码中没有注册一个 logger,注入器会设置该logger的值为null。

总结

本章,学习了 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 ClassProvider(Car),
    const ClassProvider(Engine),
    const ClassProvider(Tires),
    heroServiceProvider,
    const ClassProvider(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.getAll()[0];
  }

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

Injector本身是一个可注入的服务。

在这个例子中,Angular 把组件自身的Injector注入到了组件的构造函数中。 然后,组件在ngOnInit()中向注入的注入器请求它所需的服务。

注意,这些服务本身没有注入到组件,它们是通过调用injector.get()获得的。

get()方法如果不能解析所请求的服务,会抛出错误。调用 get() 时,还可以使用第二个参数,它是当服务不存在时该返回的值。如果没有在当前或任何祖先注入器中注册过,Angular 找不到服务。

这种方法是服务定位器模式的一个范例。

避免使用此技术,除非确实需要它。它倡导了一个粗糙的存取方法,就像在这里看到的。它难以解释、理解和测试。你不能通过检查构造函数,来知道这个类需要什么或者它要做什么。它可以从任何祖先组件中获得服务,而不仅仅是它自己。你会被迫研究它的实现,才可能明白它都做了什么。

当框架开发人员必须通用地、动态地获取服务时,可能会采用这个方法。

下一步

模板语法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容