《英雄指南》继续前行。接下来,我们准备添加更多的组件。

将来会有更多的组件访问英雄数据,我们不想一遍一遍地复制粘贴同样的代码。 解决方案是,创建一个单一的、可复用的数据服务,然后学着把它注入到那些需要它的组件中去。

我们将重构数据访问代码,把它隔离到一个独立的服务中去,让组件尽可能保持精简,专注于为视图提供支持。 在这种方式下,借助模拟服务来对组件进行单元测试也会更容易。

因为数据服务通常都是异步的,我们将在本章创建一个基于承诺 (Promise)的数据服务。

当然,一开始我们还是要让我们的程序运行起来。在终端输入

npm start

创建英雄服务

客户向我们描绘了本应用更大的目标:想要在不同的页面中用多种方式显示英雄。 现在我们已经能从列表中选择一个英雄了,但这还不够。 很快,我们将添加一个仪表盘来显示表现最好的英雄,并创建一个独立视图来编辑英雄的详情。 所有这些视图都需要英雄数据。

目前,AppComponent显示的是模拟数据。 至少有两个地方可以改进: 首先,定义英雄的数据不该是组件的任务; 其次,想把这份英雄列表的数据共享给其它组件和视图可不那么容易。

我们可以把获取英雄数据的任务重构为一个单独的服务,它将提供英雄数据,并把服务在所有需要英雄数据的组件间共享。

创建 HeroService

app目录下创建一个名叫hero.service.ts的文件。

我们的文件名遵循的规则是,小写的服务器名称加上.service后缀,如果服务名称包含多个单词,我们就把基本名部分写成中线形式 (dash-case)。 例如,SpecialSuperHeroService服务应该被定义在special-super-hero.service.ts文件中。

我们把这个类命名为HeroService,并导出它,以供别人使用。

app/hero.service.ts

import { Injectable } from '@angular/core';@Injectable()
export class HeroService {
}

View Code

可注入的服务

注意,我们导入了 Angular 的Injectable函数,并作为@Injectable()装饰器使用这个函数。

不要忘了写圆括号!如果忘了写,就会导致一个很难诊断的错误。

当 TypeScript 看到@Injectable()装饰器时,就会记下本服务的元数据。 如果 Angular 需要往这个服务中注入其它依赖,就会使用这些元数据。

获取英雄数据

添加一个名叫getHeros的方法。

@Injectable()
export class HeroService {getHeroes(): void {} // stub
}

View Code

在这个实现上暂停一下,我们先来讲一个重点。

数据使用者并不知道本服务会如何获取数据。 我们的HeroService服务可以从任何地方获取英雄的数据。 它可以从网络服务器获取,可以从浏览器的局部存储区获取,也可以从模拟的数据源。

这就是从组件中移除数据访问代码的美妙之处。 这样我们可以随时改变数据访问的实现方式,而无需对使用英雄的组件作任何改动。

模拟英雄数据

我们曾在AppComponent组件中写过模拟数据。它不该在那里,但也不该在这里! 我们应把模拟数据移到它自己的文件中去。

app.component.ts文件中剪切HEROS数组,把它粘贴到app目录下一个名叫mock-heroes.ts的文件中。 还要复制import {Hero}...语句,因为我们的英雄数组用到了Hero

app/mock-heroes.ts

import { Hero } from './hero';
export const HEROES: Hero[] = [{id: 11, name: 'Mr. Nice'},{id: 12, name: 'Narco'},{id: 13, name: 'Bombasto'},{id: 14, name: 'Celeritas'},{id: 15, name: 'Magneta'},{id: 16, name: 'RubberMan'},{id: 17, name: 'Dynama'},{id: 18, name: 'Dr IQ'},{id: 19, name: 'Magma'},{id: 20, name: 'Tornado'}
];

View Code

我们导出了HEROES常量,以便可以在其它地方导入它 — 例如HeroService服务。

同时,回到刚剪切出HEROES数组的app.component.ts文件,我们留下了一个尚未初始化的heroes属性:

heroes: Hero[];

返回模拟的英雄数据

回到HeroService,我们导入HEROES常量,并在getHeroes方法中返回它。 我们的HeroService服务现在看起来是这样:

import { Injectable } from '@angular/core';import { Hero } from './hero';
import { HEROES } from './mock-heroes';@Injectable()
export class HeroService {getHeroes(): Hero[] {return HEROES;}
}

View Code

使用 HeroService 服务

我们可以在多个组件中使用 HeroService 服务了,先从 AppComponent 开始。

通常,我们先导入要用的东西,例如HeroService

import { HeroService } from './hero.service';

导入这个服务让我们可以在代码中引用它。 AppComponent该如何在运行中获得一个具体的HeroService实例呢?

我们要自己 new 出这个 HeroService 吗?不!

尽管我们可以使用new来创建HeroService的实例,就像这样:

heroService = new HeroService(); // 不是这样的

但这不是个好主意,有很多理由,例如:

  • 我们的组件得弄清楚该如何创建HeroService。 如果有一天我们修改了HeroService的构造函数,我们不得不找出创建过此服务的每一处代码,并修改它。 围着补丁代码转圈很容易导致错误,还会增加测试负担。

  • 我们每次使用new都会创建一个新的服务实例。 如果这个服务需要缓存英雄列表,并把这个缓存共享给别人呢?怎么办? 没办法,做不到。

  • 我们把AppComponent锁定到HeroService的一个特定实现。 我们很难在不同的场景中切换实现。 例如,能离线操作吗?能在测试时使用不同的模拟版本吗?这可不容易。

  • 如果……如果……嘿!这下我们可有得忙了!

有办法了,真的!这个办法真是简单得不可思议,它能解决这些问题,你就再也没有犯错误的借口了。

注入 HeroService

用这两行代码代替用new时的一行:

  1. 添加一个构造函数,并定义一个私有属性。

  2. 添加组件的providers元数据。

下面就是这个构造函数:

constructor(private heroService: HeroService) { }

构造函数自己什么也不用做,它在参数中定义了一个私有的heroService属性,并把它标记为注入HeroService的靶点。

现在,当创建AppComponent实例时,Angular 知道需要先提供一个HeroService的实例。

看到这里,是不是感到很兴奋很熟悉,构造函数注入。

注入器还不知道该如何创建HeroService。 如果现在运行我们的代码,Angular 就会失败,并报错:

EXCEPTION: No provider for HeroService! (AppComponent -> HeroService)
(异常:没有 HeroService 的提供商!(AppComponent -> HeroService))

View Code

有么有?

我们还得注册一个HeroService提供商,来告诉注入器如何创建HeroService。 要做到这一点,我们在@Component组件的元数据底部添加providers数组属性如下:

providers:[HeroService]

providers数组告诉 Angular,当它创建新的AppComponent组件时,也要创建一个HeroService的新实例。 AppComponent会使用那个服务来获取英雄列表,在它组件树中的每一个子组件也同样如此。

AppComponent 中的 getHeroes

我们已经有了服务,并把它存入了私有变量heroService中。我们这就开始使用它。

停下来想一想。我们可以在同一行内调用此服务并获得数据。

this.heroes = this.heroService.getHeroes();

在真实的世界中,我们并不需要把一行代码包装成一个专门的方法,但无论如何,我们在演示代码中先这么写:

getHeros():void{

this.heroes = this.heroService.getHeroes();

}

ngOnInit 生命周期钩子

毫无疑问,AppComponent应该获取英雄数据并显示它。 我们该在哪里调用getHeroes方法呢?在构造函数中吗? 不 !

多年的经验和惨痛的教训教育我们,应该把复杂的逻辑扔到构造函数外面去, 特别是那些需要从服务器获取数据的逻辑更是如此。

构造函数是为了简单的初始化工作而设计的,例如把构造函数的参数赋值给属性。 它的负担不应该过于沉重。我们应该能在测试中创建一个组件,而不用担心它会做实际的工作 — 例如和服务器通讯,直到我们主动要求它做这些。

如果不在构造函数中,总得有地方调用getHeroes吧。

这也不难。只要我们实现了 Angular 的 ngOnInit 生命周期钩子,Angular 就会主动调用这个钩子。 Angular提供了一些接口,用来介入组件生命周期的几个关键时间点:刚创建时、每次变化时,以及最终被销毁时。

每个接口都有唯一的一个方法。只要组件实现了这个方法,Angular 就会在合适的时机调用它。

这是OnInit接口的基本轮廓:

import { OnInit } from '@angular/core';export class AppComponent implements OnInit {ngOnInit(): void {}
}

View Code

我们写了一个带有初始化逻辑的ngOnInit方法,Angular会在适当的时候调用它。 在这个例子中,我们通过调用getHeroes来完成初始化。

ngOnit():void{
this.getHeroes();
}

View Code

我们的应用将会像期望的那样运行,显示英雄列表,并且在我们点击英雄的名字时,显示英雄的详情。

我们就快完成了,但还有点事情不太对劲。

异步服务与承诺

我们的HeroService立即返回一个模拟的英雄列表,它的getHeroes函数签名是同步的。

this.heroes = this.heroService.getHeroes();

请求英雄数据,返回结果中就有它们了。

将来,我们打算从远端服务器上获取英雄数据。我们还没调用 http,但在后面的章节中我们会希望这么做。

那时候,我们不得不等待服务器响应,并且在等待过程中我们无法阻塞用户界面响应, 即使我们想这么做(也不应这么做)也做不到,因为浏览器不会阻塞。(这里为什么要提到堵塞,因为后面有一个及时搜索显示结果的例子)

我们不得不使用一些异步技术,而这将改变getHeroes方法的签名。

我们将使用 承诺 。

HeroService会生成一个承诺

承诺 就是 …… 好吧,它就是一个承诺,在有了结果时,它承诺会回调我们。 我们请求一个异步服务去做点什么,并且给它一个回调函数。 它会去做(在某个地方),一旦完成,它就会调用我们的回调函数,并通过参数把工作结果或者错误信息传给我们。

HeroServicegetHeroes方法改写为返回承诺的形式:

getHeroes():Promise<Hero[]>{return Promise.resolve(HEROES);
}

我们继续使用模拟数据。我们通过返回一个 立即解决的承诺 的方式,模拟了一个超快、零延迟的超级服务器。

基于承诺的行动

回到AppComponent和它的getHeroes方法,我们看到它看起来还是这样的:

app/app.component.ts (getHeroes - old)

  getHeroes(): void {this.heroes = this.heroService.getHeroes();}

View Code

在修改了HeroService之后,我们还要把this.heroes替换为一个承诺,而不再是一个英雄数组。

我们得修改这个实现,把它变成基于承诺的,并在承诺的事情被解决时再行动。 一旦承诺的事情被成功解决,我们就会显示英雄数据。

我们把回调函数作为参数传给承诺对象的then方法:

app/app.component.ts (getHeroes - revised)

getHeroes(): void {this.heroService.getHeroes().then(heroes => this.heroes = heroes);
}

View Code

在回调函数中,我们把服务返回的英雄数组赋值给组件的heroes属性。是的,这就搞定了。

我们的程序仍在运行,仍在显示英雄列表,在选择英雄时,仍然会把它/她显示在详情页面中。

下面是本章讨论过的代码文件:

app/hero.service.ts

import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
@Injectable()
export class HeroService {getHeroes(): Promise<Hero[]> {return Promise.resolve(HEROES);}
}

View Code

app/app.component.ts

import { Component, OnInit } from '@angular/core';
import { Hero } from './hero';
import { HeroService } from './hero.service';
@Component({selector: 'my-app',template: `<h1>{{title}}</h1><h2>My Heroes</h2><ul class="heroes"><li *ngFor="let hero of heroes"[class.selected]="hero === selectedHero"(click)="onSelect(hero)"><span class="badge">{{hero.id}}</span> {{hero.name}}</li></ul><my-hero-detail [hero]="selectedHero"></my-hero-detail>`,styles: [`.selected {background-color: #CFD8DC !important;color: white;}.heroes {margin: 0 0 2em 0;list-style-type: none;padding: 0;width: 15em;}.heroes li {cursor: pointer;position: relative;left: 0;background-color: #EEE;margin: .5em;padding: .3em 0;height: 1.6em;border-radius: 4px;}.heroes li.selected:hover {background-color: #BBD8DC !important;color: white;}.heroes li:hover {color: #607D8B;background-color: #DDD;left: .1em;}.heroes .text {position: relative;top: -3px;}.heroes .badge {display: inline-block;font-size: small;color: white;padding: 0.8em 0.7em 0 0.7em;background-color: #607D8B;line-height: 1em;position: relative;left: -1px;top: -4px;height: 1.8em;margin-right: .8em;border-radius: 4px 0 0 4px;}`],providers: [HeroService]
})
export class AppComponent implements OnInit {title = 'Tour of Heroes';heroes: Hero[];selectedHero: Hero;constructor(private heroService: HeroService) { }getHeroes(): void {this.heroService.getHeroes().then(heroes => this.heroes = heroes);}ngOnInit(): void {this.getHeroes();}onSelect(hero: Hero): void {this.selectedHero = hero;}
}

View Code

app/mock-heroes.ts

import { Hero } from './hero';
export const HEROES: Hero[] = [{id: 11, name: 'Mr. Nice'},{id: 12, name: 'Narco'},{id: 13, name: 'Bombasto'},{id: 14, name: 'Celeritas'},{id: 15, name: 'Magneta'},{id: 16, name: 'RubberMan'},{id: 17, name: 'Dynama'},{id: 18, name: 'Dr IQ'},{id: 19, name: 'Magma'},{id: 20, name: 'Tornado'}
];

View Code

走过的路

来盘点一下我们完成了什么。

  • 我们创建了一个能被多个组件共享的服务类。

  • 我们使用了ngOnInit生命周期钩子,以便在AppComponent激活时获取英雄数据。

  • 我们把HeroService定义为AppComponent的一个提供商。

  • 我们创建了模拟的英雄数据,并把它导入我们的服务中。

  • 我们把服务设计为返回承诺,组件从承诺中获取数据。

前方的路

通过使用共享组件和服务,我们的《英雄指南》更有复用性了。 我们还要创建一个仪表盘,要添加在视图间路由的菜单链接,还要在模板中格式化数据。 随着我们应用的进化,我们还会学到如何进行设计,让它更易于扩展和维护。

我们将在下一章学习 Angular 组件路由,以及在视图间导航的知识。

附件:慢一点

我们可以模拟慢速连接。

导入Hero类,并且在HeroService中添加如下的getHeroesSlowly方法:

getHeroesSlowly(): Promise<Hero[]> {return new Promise<Hero[]>(resolve =>setTimeout(resolve, 2000)) // delay 2 seconds.then(() => this.getHeroes());
}

View Code

getHeroes一样,它也返回一个承诺。 但是,这个承诺会在提供模拟数据之前等待两秒钟。

回到AppComponent,用heroService.getHeroesSlowly替换heroService.getHeroes,并观察应用的行为。

下一步,我们将学习路由

转载于:https://www.cnblogs.com/Playfunny/p/6150573.html

NG2-我们创建一个可复用的服务来调用英雄的数据相关推荐

  1. 如何创建一个标准的Windows服务

    如何创建一个标准的Windows服务 Posted on 2009-02-11 13:08 伍华聪 阅读(4693) 评论(16) 编辑 收藏 在很多时候,我们需要一个定时器,当间隔某段时间或者在某一 ...

  2. [贝聊科技]iOS 代码架构(一)如何创建一个易复用的组件

    前言 贝聊的移动客户端分别有家长端和老师端,一家公司里同时维护多个业务上有关联性的app这种情况其实很常见,例如一些提供 O2O 服务的公司,经常会分用户端和商家端.这些客户端虽然各自负责着一个业务环 ...

  3. 使用Axis2创建一个简单的WebService服务

    使用过Java进行过WebService开发都会听过或者接触过Apache Axis2,Axis2框架是应用最广泛的WebService框架之一了. 这里使用Axis2来开发和部署一个最简单的WebS ...

  4. ROS学习笔记10(创建一个ROS消息和服务)

    这篇教程主要介绍怎样创建和编译一个msg消息和srv服务文件,同时介绍rosmsg,rossrv,roscp等命令工具. 文章目录 1 msg和srv文件长什么样 2 msg使用 2.1 创建一个ms ...

  5. OPNET学习笔记(一):创建一个小型局域网工程、场景并对比统计数据

    OPNET学习笔记(一):创建一个小型局域网并对比统计数据 前言 1.创建工程 2.配置场景 3.创建场景 4.选择统计量 5.结果显示 6.创建对比场景并对比 7.总结 前言 关于OPNET的安装教 ...

  6. 你的智能音箱为什么无所不能?如何创建一个自己的音箱服务

    在智能音箱越来越普及的现在,围绕音箱而生的生态服务也愈加蓬勃,开发者如何才能拥抱音箱生态,制作自己的语音应用服务?我们调研了以京东小京鱼为主的平台,梳理了语音应用的实现方式.(原文地址:http:// ...

  7. Android创建一个无启动页服务

    1.启动页添加主题 android:theme="@style/AppTheme.NoStartupPage" 2.自定义主题样式 3.MianActivity 4.创建服务 5. ...

  8. 使用crow创建一个c++的web服务

    安装参考地址 在安装的过程中会出ssl一个库没有安装apt-get install libssl-dev CMakeList.txt cmake_minimum_required(VERSION 3. ...

  9. 使用C#创建一个简单的Windows服务

    http://www.mzwu.com/article.asp?id=1729 转载于:https://www.cnblogs.com/taizhouxiaoba/archive/2011/02/18 ...

  10. java 中创建数据端口_java 如何在服务器端用socket创建一个监听端口,并对接受的数据进行处理,端口号为3333,请高手指点一下...

    匿名用户 1级 2011-09-10 回答 我百度HI你好了 public class Test { public static void main(String[] args) { Test1 t= ...

最新文章

  1. 【译】Swift算法俱乐部-Boyer-Moore字符串搜索
  2. 线段树、二叉堆以及离散化入门
  3. Deep Learning论文笔记之(三)单层非监督学习网络分析
  4. 栈的基础概念与经典题目(Leetcode题解-Python语言)
  5. Spark源码分析之TaskSetManager分析
  6. java线程通讯的方式
  7. yoga11rt系统刷linux,【攻略贴】联想Yoga“一秒”变身安卓平板,Win8 Andriod双系统刷机攻略出炉!...
  8. Ajax提交与传统表单提交的区别说明
  9. python 随机请求头_为了爬虫换个头,我用python实现三种随机请求头方式!
  10. Linux系统编程 -- IO缓冲区
  11. 16.凤凰架构:构建可靠的大型分布式系统 --- 向微服务迈进
  12. java应用中如何连接dbproxy_GitHub - alchemystar/hero: 用c语言写的dbproxy
  13. Matlab下的整数规划(CVX)
  14. odoo报表内部和外部布局
  15. ArcEngine(五)用ICommand接口实现放大缩小
  16. IOS 企业级苹果开发者账号申请流程
  17. 嵌入式的汉字原来是这样显示的?
  18. ISE FPGA时钟系统
  19. 5G+物联网商业模式促使物联网卡迎来增长新风口
  20. 深入Android源码系列(二) HOOK技术大作战

热门文章

  1. C++ 传递指针给函数
  2. STM 事务 ACID
  3. stat /bin/bash: no such file or directory“: unknown.
  4. 7723java之战,满江红4之江山美人
  5. linux ffmpeg插件,Linux FFmpeg(含x264、lame插件)安装记录
  6. JavaSE基础———对象数组和集合Collection
  7. Unity WIndows语音识别(一)关键字识别
  8. java基础总结06-常用api类-System类常用方法
  9. 一文带你了解微信/支付宝支付的相关概念
  10. CentOS 7.2.5 安装 Redis 与 远程访问