随着“英雄之旅”应用的发展,您将添加更多须要访问英雄数据的组件。css
不是一遍又一遍复制和粘贴相同的代码,而是建立一个可重用的数据服务,并将其注入到须要它的组件中。 使用单独的服务可以使组件保持精简并专一于支持视图,并使用模拟服务对组件进行单元测试变得容易。html
由于数据服务老是异步的,因此您将使用数据服务的基于Future的版原本完成页面。java
当你完成这个页面,应用程序应该看起来像这个实例(查看源代码)。git
在继续英雄之旅以前,请确认您具备如下结构。 若是没有,请返回前面的页面。github
若是该应用程序还没有运行,请启动该应用程序。 在进行更改时,请经过从新加载浏览器窗口来保持运行。web
利益相关者但愿以不一样的页面以各类方式展现英雄。 用户能够从列表中选择一个英雄。 不久,您将添加一个仪表板与顶尖的表演英雄,并建立一个单独的视图编辑英雄的细节。 全部三个视图都须要英雄数据。编程
目前,AppComponent定义了模拟英雄的显示。 然而,定义英雄不是组件的工做,你不能轻易与其余组件和视图共享英雄名单。 在这个页面中,您将把英雄数据采集业务转移到一个提供数据的服务中,并与须要数据的全部组件共享该服务。api
在lib / src下建立文件hero_service.dart。浏览器
服务文件的命名约定是小写的服务名称,后跟_service。 对于多词服务名称,请使用小写的snake_case。 例如,SpecialSuperHeroService的文件名是special_super_hero_service.dart。缓存
命名类HeroService。lib/src/hero_service.dart (empty class)
import 'package:angular/angular.dart'; @Injectable() class HeroService { }
注意你使用了@Injectable()注解。 这告诉Angular编译器,HeroService将成为注入的候选者(更多关于这个)。
HeroService能够从任何地方(Web服务,本地存储或模拟数据源)获取英雄数据。 如今,导入Hero和mockHeroes,并从getHeroes()方法返回模拟英雄:lib/src/hero_service.dart
import 'package:angular/angular.dart'; import 'hero.dart'; import 'mock_heroes.dart'; @Injectable() class HeroService { List<Hero> getHeroes() => mockHeroes; }
您已经准备好在其余组件中使用HeroService,从AppComponent开始。
导入HeroService,以便您能够在代码中引用它。lib/app_component.dart (hero service import)
import 'src/hero_service.dart';
不要使用new实例化HeroService
AppComponent应该如何获取HeroService的实例?
你可能会像这样建一个HeroService的新实例:lib/app_component.dart (excerpt)
HeroService heroService = new HeroService(); // DON'T do this
可是,这个选项并不理想,缘由以下:
注入HeroService
而不是使用新的表达式,添加这些行:
这里是属性和构造函数:lib/app_component.dart (constructor)
final HeroService _heroService; AppComponent(this._heroService);
构造函数除了设置_heroService属性外什么也不作。 _heroService的HeroService类型将构造函数的参数标识为HeroService注入点。
如今Angular知道在建立一个新的AppComponent时要提供一个HeroService实例。
在依赖注入页面阅读更多关于依赖注入的内容。
注入器不知道如何建立一个HeroService。 若是您如今运行代码,Angular会失败并显示如下错误:
EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
为了教导注入器如何建立HeroService,请添加如下提供程序列表做为@Component注解的最后一个参数。lib/app_component.dart (providers)
providers: const [HeroService],
providers参数告诉Angular在建立一个AppComponent时建立一个HeroService的新实例。 AppComponent及其子组件可使用该服务来获取英雄数据。
添加一个getHeroes()方法到应用程序组件,并删除英雄初始值设定项:lib/app_component.dart (heroes and getHeroes)
List<Hero> heroes; void getHeroes() { heroes = _heroService.getHeroes(); }
AppComponent应该能够获取并显示英雄数据,而不会出现问题。
您可能会试图在构造函数中调用getHeroes()方法,但构造函数不该包含复杂的逻辑,特别是调用服务器的构造函数(如数据访问方法)。 构造函数用于简单的初始化,如将构造函数参数链接到属性。
要用Angular调用getHeroes(),能够实现Angular ngOnInit生命周期钩子。 Angular为组件生命周期中的关键时刻提供接口:建立,每次更改以后,最终销毁。
每一个接口都有一个方法。 当组件实现该方法时,Angular会在适当的时候调用它。
在“Lifecycle Hooks”页面中详细了解生命周期挂钩。
将OnInit添加到由AppComponent实现的接口列表中,并使用里面的初始化逻辑编写一个ngOnInit()方法。 Angular会在正确的时间调用它。 在这种状况下,经过调用getHeroes()来初始化。
class AppComponent implements OnInit { void ngOnInit() => getHeroes(); }
刷新浏览器。 当你点击一个英雄名字时,应用程序应该显示英雄名单和英雄详情视图。
HeroService当即返回模拟英雄列表; 它的getHeroes()签名是同步的。
lib/src/hero_service.dart (getHeroes)
List<Hero> getHeroes() => mockHeroes;
最终,英雄数据未来自远程服务器。 当使用远程服务器时,用户没必要等待服务器响应; 此外,您在等待期间没法阻塞用户界面。
为了协调视图和响应,你可使用Futures,这是一个改变getHeroes()方法签名的异步技术。
Future表明将来的计算或值。 使用Future,您能够注册回调函数,在计算完成时(结果准备就绪),或须要报告计算错误时调用。
这是一个简单的解释。 在“Asynchronous Programming: Futures”的Dart语言教程中阅读更多有关Futures的信息。
添加dart:async的导入,由于它定义了Future,并使用这个Future返回的getHeroes()方法更新HeroService:lib/src/hero_service.dart (excerpt)
Future<List<Hero>> getHeroes() async => mockHeroes;
你还在模拟数据。 你正在模拟一个超快,零延迟的服务器的行为,经过返回一个模拟英雄当即可用的Future。
将方法标记为async会自动将返回类型设置为Future。 有关异步函数的更多信息,请参阅在Dart语言浏览中声明异步函数。
因为对HeroService的更改,应用程序组件的英雄属性如今是Future,而不是英雄列表。 您必须更改实现以在完成时处理Future结果。 当Future成功完成时,您将显示英雄。
这是当前的实现:lib/app_component.dart (synchronous getHeroes)
void getHeroes() { heroes = _heroService.getHeroes(); }
将回调函数做为参数传递给Future.then()方法:lib/app_component.dart (asynchronous getHeroes)
void getHeroes() { _heroService.getHeroes().then((heroes) => this.heroes = heroes); }
该回调将组件的英雄属性设置为服务返回的英雄列表。刷新浏览器。 该应用程序仍然运行,显示英雄列表,并响应名称选择与详细信息视图。
包含一个或多个Future.then()方法的异步方法可能难以阅读和理解。 谢天谢地,Dart的异步/等待语言功能可让你编写看起来就像同步代码的异步代码。 重写getHeroes():lib/app_component.dart (revised async/await getHeroes)
Future<Null> getHeroes() async { heroes = await _heroService.getHeroes(); }
Future <Null>返回类型是异步void的等价物。
在Dart语言教程的Asynchronous Programming:Futures的Async和await部分阅读更多关于使用async / await进行异步编程的内容。
在本页的末尾, Appendix: Take it slow描述应用程序可能与不良链接相似。
在全部重构以后验证您是否具备如下结构:
这里是本页讨论的代码文件。
lib/src/hero_service.dart
import 'dart:async'; import 'package:angular/angular.dart'; import 'hero.dart'; import 'mock_heroes.dart'; @Injectable() class HeroService { Future<List<Hero>> getHeroes() async => mockHeroes; }
lib/app_component.dart
import 'dart:async'; import 'package:angular/angular.dart'; import 'src/hero.dart'; import 'src/hero_detail_component.dart'; import 'src/hero_service.dart'; @Component( selector: 'my-app', templateUrl: 'app_component.html', styleUrls: const ['app_component.css'], directives: const [CORE_DIRECTIVES, HeroDetailComponent], providers: const [HeroService], ) class AppComponent implements OnInit { final title = 'Tour of Heroes'; final HeroService _heroService; List<Hero> heroes; Hero selectedHero; AppComponent(this._heroService); Future<Null> getHeroes() async { heroes = await _heroService.getHeroes(); } void ngOnInit() => getHeroes(); void onSelect(Hero hero) => selectedHero = hero; }
如下是您在此页面中所取得的成果:
英雄之旅已经变得更加可重复使用共享组件和服务。 下一个目标是建立一个仪表板,添加在视图之间路由的菜单连接,以及在模板中格式化数据。 随着应用程序的发展,你会发现如何设计它,使其更容易成长和维护。
阅读下一个教程页面中有关Angular组件路由器和视图之间的导航。
要模拟一个缓慢的链接,请将如下getHeroesSlowly()方法添加到HeroService。
lib/src/hero_service.dart (getHeroesSlowly)
Future<List<Hero>> getHeroesSlowly() { return new Future.delayed(const Duration(seconds: 2), getHeroes); }
像getHeroes()同样,它也返回一个Future,可是这个Future在完成前等待两秒钟。
回到AppComponent中,用getHeroesSlowly()替换getHeroes(),看看应用程序的行为。