依赖注入是一个重要的应用程序设计模式。 它的用途很是普遍,几乎全部人都称之为DI。html
Angular拥有本身的依赖注入框架,若是没有它,你真的不能构建一个Angular应用程序。java
本页面涵盖了DI是什么,为何它是有用的,以及如何使用Angular DI。git
要理解为何依赖注入如此重要,请考虑没有它的例子。 想象一下写下面的代码: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写测试的时候,你会隐藏它的依赖关系。 在测试环境中甚至能够建立一个新的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 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数据的每个其余用途。
最好把关于英雄数据访问的细节隐藏在本身定义的服务类的文件中。
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。
这里是修改后的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()函数。
应用程序在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服务很是简单: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()的类时,注入器会报错。
注入器也负责实例化像HeroesComponent这样的组件。 为何不是HeroesComponent标记为@Injectable()?
你能够添加它,若是你真的想。 这是没有必要的,由于HeroesComponent已经被标记了@Component,而且这个标注类(像@Directive和@Pipe,稍后你会学到)是Injectable的子类型。 事实上,Injectable注释将类标识为注入器实例化的目标。
老是包含括号
老是要写成@Injectable(),而不只仅是@Injectable。 元数据注解必须是对编译时常量变量的引用,或对Injectable()等常量构造函数的调用。
若是忘记括号,分析器将会抱怨:“注解建立必须有参数”。 若是您尝试运行应用程序,它将没法正常工做,控制台会说“表达式必须是编译时常量”。
服务提供者提供依赖性值的具体运行时版本。 注入器依靠提供者建立注入器注入组件,指令,管道和其余服务的服务实例。
您必须使用注入器注册服务provider,不然将不知道如何建立服务。
接下来的几节将解释你能够注册一个提供者的许多方法。
有不少方法能够提供实现Logger的东西。 记录器类自己是一个显而易见的原生提供者。
providers: const [Logger]
但这不是惟一的方法。
您能够配置一个能够传递Logger的注入器代替供应商,你能够提供一个替代类。
你能够给它一个调用一个记录器工厂函数的提供者,在正确的状况下,任何这些方法均可能是一个不错的选择。
重要的是,注入器有一个提供者,当它须要一个Logger。
再次,这是Provider类的语法。
providers: const [Logger]
这其实是使用Provider类实例进行提供者注册的简写表达式:
const [const Provider(Logger, useClass: Logger)]
第一个Provider构造函数参数是做为定位依赖项值和注册提供者的键的标记。
第二个是一个命名参数,好比useClass,你能够把它看做是建立依赖关系值的方法。 有不少方法能够建立依赖关系值,就像写许多配方的方法同样。
偶尔你会要求不一样的类提供服务。 如下代码告诉注入器在有事要求Logger时返回BetterLogger。
const [const Provider(Logger, useClass: BetterLogger)]
当在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。 这样一个令牌的定义以下所示:
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将没法找到该服务。