AngularDart4.0 指南- 依赖注入 顶

依赖注入是一个重要的应用程序设计模式。 它的用途非常广泛,几乎所有人都称之为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类和Tires类中实例化自己的副本。

如果Engine类发展而它的构造函数需要一个参数呢? 这将打破汽车类,它会保持中断,直到你改写engine = new Engine(theNewParameter)的行。 当你第一次写“Car”时,Engine构造参数甚至不是一个考虑因素。 即使是现在,你也不可能预料到它们。 但是你必须开始关心,因为当Engine定义改变时,Car类必须改变。 这使得Car变得脆弱。

如果你想在你的Car上装一个不同品牌的轮胎怎么办? 太糟糕了。 你被锁定在Tires 班制造的任何品牌上。 这使得Car类不灵活。

现在每辆新车都有自己的引擎。 它不能与其他车辆共享一个引擎。 虽然这对于汽车发动机是有意义的,但是您肯定可以考虑应该共享的其他依赖性,例如与制造商服务中心的机载无线连接。 Car缺乏共享以前为其他消费者创建的服务的灵活性。

当你为Car写测试的时候,你会隐藏它的依赖关系。 在测试环境中甚至可以创建一个新的Engine? Engine是依赖于什么的? 这个依赖依赖于什么? 引擎的新实例是否会对服务器进行异步调用? 你当然不希望在测试过程中发生这种情况。

如果汽车在轮胎压力低的时候应该发出警告信号呢? 如果您在测试过程中无法换上低压轮胎,您如何确认它实际上会闪烁警告?

你无法控制汽车的隐藏依赖。 当你无法控制依赖时,一个类变得很难测试。

你如何使汽车更强大,更灵活和可测试?

这太容易了。 将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实例。 不幸的是,如果你试图用useClass将OldLogger别名到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,但工厂函数却可以。

您将Logger和UserService都注入到工厂提供程序中,让注入器将它们传递给工厂函数:

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

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

useFactory字段告诉Angular提供者是一个工厂函数,其实现是heroServiceFactory。 deps属性是提供者令牌的列表。 Logger和UserService类用作其自己的类提供程序的标记。 注入器解析这些令牌并将相应的服务注入匹配的工厂功能参数。

请注意,您在一个常量,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)

当您考虑大多数依赖关系值由类提供时,这是特别方便的。

非类依赖关系

如果依赖性值不是一个类呢? 有时你想注入的东西是一个string,list,map,或者一个function。

应用程序通常会定义具有许多小事实(例如应用程序标题或Web API端点地址)的配置对象,但这些配置对象并不总是类的实例。 他们可以像这样的地图文字:

lib/src/app_config.dart (excerpt)

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

如果你想使这个配置对象可用于注入呢? 您知道您可以向值提供者注册一个对象。

但是,你应该使用什么作为令牌? 你没有一个类作为一个令牌; 没有HeroDiConfig类。

虽然你可以使用Map,但是你不应该因为(像String)Map太普遍。 您的应用程序可能依赖于几个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将无法找到该服务。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏王磊的博客

Java核心(三)并发中的线程同步与锁

乐观锁、悲观锁、公平锁、自旋锁、偏向锁、轻量级锁、重量级锁、锁膨胀...难理解?不存的!来,话不多说,带你飙车。

1132
来自专栏JavaEdge

线程安全与锁优化1 线程安全2 锁优化

3439
来自专栏QQ音乐技术团队的专栏

打通Android Gradle编译过程的任督二脉

本文主要是基于自己在工作当中的一些Android Gradle实践经验,对gradle相关知识做的一个简单总结和分享,希望对大家有帮助。 首先会讲Gradle大...

1.4K9
来自专栏码洞

Java高阶必备之Netty基础原理

Netty是Java程序员通向高阶之路必须要过的门槛之一。干了几年的Java程序员发现业务开发似乎就是在SSH的世界里摸滚打爬的时候,会开始感到迷茫,难道程序员...

972
来自专栏JAVA高级架构开发

成为Java顶尖程序员,先过了下面问题!

ArrayList和LinkedList内部的实现大致是怎样的?他们之间的区别和优缺点?

2340
来自专栏阿杜的世界

Java Web技术经验总结(十三)

902
来自专栏安恒网络空间安全讲武堂

堆利用之double-free

2434
来自专栏Golang语言社区

Go Channel 应用模式(一)

Channel是Go中的一种类型,和goroutine一起为Go提供了并发技术, 它在开发中得到了广泛的应用。Go鼓励人们通过Channel在goroutine...

1802
来自专栏Java开发者杂谈

Spring @Transactional踩坑记

@Transactional踩坑记 总述 ​ Spring在1.2引入@Transactional注解, 该注解的引入使得我们可以简单地通过在方法或者类上添加@...

7377
来自专栏美团技术团队

不可不说的Java“锁”事

Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景进行举例,为...

1182

扫码关注云+社区