专栏首页Google DartAngularDart4.0 英雄之旅-教程-06服务 顶

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 条评论
登录 后参与评论

相关文章

  • AngularDart4.0 英雄之旅-教程-07路由 顶

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

    南郭先生
  • AngularDart4.0 英雄之旅-教程-08HTTP 顶

    你离开的地方 在前一页中,您学会了在仪表板和固定英雄列表之间导航,沿途编辑选定的英雄。 这是这个页面的起点。

    南郭先生
  • Flutte部件目录-基本部件(三) 顶

    要显示snackbar或持久底部表,请通过Scaffold.of获取当前BuildContext的ScaffoldState,然后使用ScaffoldState...

    南郭先生
  • GitHub 项目推荐:俄罗斯小游戏、Markdown等

    本周新增了粉丝推荐环节,如果你有发现优质的开源项目,欢迎在公众号或其它平台私信推荐,我们会不定期筛选推送。

    GitHubDaily
  • 搭建邮件服务器和论坛

    今天一起来看看如何搭建自己的邮件服务器和论坛服务,使用的工具分别为 Ewomail 和 Discourse。

    周萝卜
  • 原始 socket 编程

    1.原始 socket 可以和内核一样直接对所有层进行操作(除了物理层)。可以更改 mac 更改 ip 更改端口。so dos 攻击就可以通过原始 socket...

    战神伽罗
  • 挖洞经验 | 看我如何接管OLX的每一条广告

    在这篇文章中,我将跟大家分享关于“不安全的直接对象引用”(IDOR)攻击的相关内容。没错,正如本文标题所写的那样,我在OLX所托管的网站上发现了一个IDOR漏洞...

    FB客服
  • 正道的光!这有个用TensorFlow做的小黄图过滤器

    相信每个人都遇到过这种情况:想下载一部电影或一个软件,结果跳出一堆色情广告,因为怕同学或同事误会,不得不赶紧关掉浏览器。这种情况广泛存在于电子书、电影等资源类网...

    机器之心
  • Adblock:简单强大的广告过滤沙盒

      之前在使用浏览器时,经常会为太多弹窗广告所困。后来师兄教我说可以用一个叫做 AddBlock 的沙盒来过滤掉广告,遂自己上网查阅了相关资料。后来发现一个特别...

    JNingWei
  • AI综述专栏 | 跨领域推荐系统文献综述(上)

    跨领域推荐系统(Cross domain recommender systems,CDRS)能够通过源领域的信息对目标领域进行辅助推荐,CDRS由三个基本要素构...

    马上科普尚尚

扫码关注云+社区

领取腾讯云代金券