AngularDart4.0 英雄之旅-教程-06服务 顶

随着“英雄之旅”应用的发展,您将添加更多需要访问英雄数据的组件。

不是一遍又一遍复制和粘贴相同的代码,而是创建一个可重用的数据服务,并将其注入到需要它的组件中。 使用单独的服务可使组件保持精简并专注于支持视图,并使用模拟服务对组件进行单元测试变得容易。

因为数据服务总是异步的,所以您将使用数据服务的基于Future的版本来完成页面。

当你完成这个页面,应用程序应该看起来像这个实例(查看源代码)。

你开始的地方

在继续英雄之旅之前,请确认您具有以下结构。 如果没有,请返回前面的页面。

如果该应用程序尚未运行,请启动该应用程序。 在进行更改时,请通过重新加载浏览器窗口来保持运行。

创建一个英雄服务

利益相关者希望以不同的页面以各种方式展示英雄。 用户可以从列表中选择一个英雄。 不久,您将添加一个仪表板与顶尖的表演英雄,并创建一个单独的视图编辑英雄的细节。 所有三个视图都需要英雄数据。

目前,AppComponent定义了模拟英雄的显示。 然而,定义英雄不是组件的工作,你不能轻易与其他组件和视图共享英雄名单。 在这个页面中,您将把英雄数据采集业务转移到一个提供数据的服务中,并与需要数据的所有组件共享该服务。

创建一个可注入的HeroService

在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。 如果您更改HeroService构造函数,则必须查找并更新您创建服务的每个位置。 在多个地方修补代码是容易出错的,并增加了测试负担。
  • 每次使用新建时都会创建一个服务。 如果服务缓存英雄,并与他人共享缓存呢? 你不能这样做。
  • 通过将AppComponent锁定到HeroService的特定实现中,切换实现用于不同的场景(如离线操作或使用不同的模拟版本进行测试)将很困难。

注入HeroService 而不是使用新的表达式,添加这些行:

  • 添加一个私人的HeroService属性。
  • 添加一个初始化私有属性的构造函数。
  • 将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及其子组件可以使用该服务来获取英雄数据。

AppComponent.getHeroes()方法

添加一个getHeroes()方法到应用程序组件,并删除英雄初始值设定项:lib/app_component.dart (heroes and getHeroes)

List<Hero> heroes;

void getHeroes() {
  heroes = _heroService.getHeroes();
}

ngOnInit生命周期钩子

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代表未来的计算或值。 使用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语言浏览中声明异步函数

处理Future

由于对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);
}

该回调将组件的英雄属性设置为服务返回的英雄列表。刷新浏览器。 该应用程序仍然运行,显示英雄列表,并响应名称选择与详细信息视图。

使用async/await

包含一个或多个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:FuturesAsync和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;
}

你做过的操作

以下是您在此页面中所取得的成果:

  • 您创建了一个可以被许多组件共享的服务类。
  • 在AppComponent激活时,您使用ngOnInit生命周期挂钩来获取英雄数据。
  • 您将HeroService定义为AppComponent的提供者。
  • 您设计了服务来返回一个Future和从未来获取数据的组件。

你的应用应该看起来像这个实例(查看源代码)。

前方的路

英雄之旅已经变得更加可重复使用共享组件和服务。 下一个目标是创建一个仪表板,添加在视图之间路由的菜单链接,以及在模板中格式化数据。 随着应用程序的发展,你会发现如何设计它,使其更容易成长和维护。

阅读下一个教程页面中有关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(),看看应用程序的行为。

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏FreeBuf

对自助提卡系统的一次代码审计

并非有意愿要审计该站,前面的走的黑盒没有过于精彩部分就不在贴上了,对于此系统站你们懂的,多说无益,这套程序是开源的,像这种自助提卡系统相信大家已经不在陌生了,很...

1963
来自专栏枕边书

代码迁移之旅(二)- 渐进式迁移方案

说在前面 这是代码迁移的第二篇文章,也是最后一篇了,由于个人原因,原来的迁移我无法继续参与了,但完整的方案我已经准备好了,在测试环境也已经可以正常进行了。 上篇...

2449
来自专栏企鹅号快讯

python环境的安装

所谓"工欲善其事,必先利其器",首先我们就要来安装一下python环境,和一款python IDE:pycharm,由于本人用的windows系统,在这里只介绍...

2456
来自专栏影子

一张图解析 编译器编译流程

39915
来自专栏Java Web

Java学习笔记(4)——并发基础

前言 当我们使用计算机时,可以同时做许多事情,例如一边打游戏一边听音乐。这是因为操作系统支持并发任务,从而使得这些工作得以同时进行。 那么提出一个问题:如果我...

3243
来自专栏小巫技术博客

推荐:Mac下高效静态代码分析神器Unstand详解

1301
来自专栏Java成神之路

【转】JAVA之网络编程

网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习。

1847
来自专栏游戏杂谈

nginx的配置笔记

nginx中每一个host都会被包含在一个server{}中,在编写nginx规则时,它支持一些基本的正则。

1163
来自专栏木东居士的专栏

从0写一个爬虫,爬取500w好友关系数据

6936
来自专栏逸鹏说道

关于全局ID,雪花(snowflake)算法的说明

上次简单的说一下:http://www.cnblogs.com/dunitian/p/6041745.html#uid C#版本的国外朋友已经封装了,大家可以去...

3798

扫码关注云+社区

领取腾讯云代金券