英雄指南的 HeroesComponent
目前获取和显示的都是模拟数据。css
本节课的重构完成以后,HeroesComponent
变得更精简,而且聚焦于为它的视图提供支持。这也让它更容易使用模拟服务进行单元测试。html
若是你但愿从 GitHub 上查看咱们提供测试的源代码,你能够访问下面的连接:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-servicesreact
组件不该该直接获取或保存数据,它们不该该了解是否在展现假数据。 它们应该聚焦于展现数据,而把数据访问的职责委托给某个服务。git
本节课,你将建立一个 HeroService
,应用中的全部类均可以使用它来获取英雄列表。 不要使用 new
来建立此服务,而要依靠 Angular 的依赖注入机制把它注入到 HeroesComponent
的构造函数中。github
服务是在多个“互相不知道”的类之间共享信息的好办法。 你将建立一个 MessageService
,而且把它注入到两个地方:api
HeroService
中,它会使用该服务发送消息。MessagesComponent
中,它会显示其中的消息。HeroService
使用 Angular CLI 建立一个名叫 hero
的服务。数组
|
该命令会在src/app/hero.service.ts
中生成HeroService
类的骨架。HeroService
类的代码以下:缓存
src/app/hero.service.ts (new service)服务器
|
注意,这个新的服务导入了 Angular 的 Injectable 符号,而且给这个服务类添加了 @
Injectable()
装饰器。 它把这个类标记为依赖注入系统的参与者之一。HeroService
类将会提供一个可注入的服务,而且它还能够拥有本身的待注入的依赖。 目前它尚未依赖,可是很快就会有了。
@
Injectable()
装饰器会接受该服务的元数据对象,就像 @
Component()
对组件类的做用同样。
HeroService
能够从任何地方获取数据:Web 服务、本地存储(LocalStorage)或一个模拟的数据源。
从组件中移除数据访问逻辑,意味着未来任什么时候候你均可以改变目前的实现方式,而不用改动任何组件。 这些组件不须要了解该服务的内部实现。
这节课中的实现仍然会提供模拟的英雄列表。
导入 Hero
和 HEROES
。
|
添加一个 getHeroes
方法,让它返回模拟的英雄列表。
|
HeroService
在要求 Angular 把 HeroService
注入到 HeroesComponent
以前,你必须先把这个服务提供给依赖注入系统。稍后你就要这么作。 你能够经过注册提供商来作到这一点。提供商用来建立和交付服务,在这个例子中,它会对 HeroService
类进行实例化,以提供该服务。
如今,你须要确保 HeroService
已经做为该服务的提供商进行过注册。 你要用一个注入器注册它。注入器就是一个对象,负责在须要时选取和注入该提供商。
默认状况下,Angular CLI 命令 ng generate service
会经过给 @
Injectable 装饰器添加元数据的形式,用根注入器将你的服务注册成为提供商。
若是你看看 HeroService
紧前面的 @
Injectable()
语句定义,就会发现 providedIn 元数据的值是 'root':
|
@
({ providedIn: 'root', })
当你在顶层提供该服务时,Angular 就会为 HeroService
建立一个单一的、共享的实例,并把它注入到任何想要它的类上。 在 @
Injectable 元数据中注册该提供商,还能容许 Angular 经过移除那些彻底没有用过的服务来进行优化。
要了解关于提供商的更多知识,参见提供商部分。 要了解关于注入器的更多知识,参见依赖注入指南。
如今 HeroService
已经准备好插入到 HeroesComponent
中了。
这是一个过渡性的代码范例,它将会容许你提供并使用 HeroService
。此刻的代码和最终代码相差很大。
HeroesComponent
打开 HeroesComponent
类文件。
删除 HEROES
的导入语句,由于你之后不会再用它了。 转而导入 HeroService
。
src/app/heroes/heroes.component.ts (import HeroService)
|
把 heroes
属性的定义改成一句简单的声明。
|
HeroService
往构造函数中添加一个私有的 heroService
,其类型为 HeroService
。
|
这个参数同时作了两件事:1. 声明了一个私有 heroService
属性,2. 把它标记为一个 HeroService
的注入点。
当 Angular 建立 HeroesComponent
时,依赖注入系统就会把这个 heroService
参数设置为 HeroService
的单例对象。
建立一个函数,以从服务中获取这些英雄数据。
|
ngOnInit
中调用它你当然能够在构造函数中调用 getHeroes()
,但那不是最佳实践。
让构造函数保持简单,只作初始化操做,好比把构造函数的参数赋值给属性。 构造函数不该该作任何事。 它固然不该该调用某个函数来向远端服务(好比真实的数据服务)发起 HTTP 请求。
而是选择在 ngOnInit 生命周期钩子中调用 getHeroes(),以后交由 Angular 处理,它会在构造出 HeroesComponent 的实例以后的某个合适的时机调用 ngOnInit。
|
刷新浏览器,该应用仍运行的一如既往。 显示英雄列表,而且当你点击某个英雄的名字时显示出英雄详情视图。
HeroService.getHeroes()
的函数签名是同步的,它所隐含的假设是 HeroService
老是能同步获取英雄列表数据。 而 HeroesComponent
也一样假设能同步取到 getHeroes()
的结果。
|
这在真实的应用中几乎是不可能的。 如今能这么作,只是由于目前该服务返回的是模拟数据。 不过很快,该应用就要从远端服务器获取英雄数据了,而那天生就是异步操做。
HeroService
必须等服务器给出响应, 而 getHeroes()
不能当即返回英雄数据, 浏览器也不会在该服务等待期间中止响应。
HeroService.getHeroes()
必须具备某种形式的异步函数签名。
它可使用回调函数,能够返回 Promise
(承诺),也能够返回 Observable
(可观察对象)。
这节课,HeroService.getHeroes()
将会返回 Observable
,由于它最终会使用 Angular 的 HttpClient.get
方法来获取英雄数据,而 HttpClient.get()
会返回 Observable
。
HeroService
Observable
是 RxJS 库中的一个关键类。
在稍后的 HTTP 教程中,你就会知道 Angular HttpClient 的方法会返回 RxJS 的 Observable
。 这节课,你将使用 RxJS 的 of()
函数来模拟从服务器返回数据。
打开 HeroService
文件,并从 RxJS 中导入 Observable
和 of
符号。
src/app/hero.service.ts (Observable imports)
|
把 getHeroes
方法改为这样:
|
of(HEROES)
会返回一个Observable<Hero[]>
,它会发出单个值,这个值就是这些模拟英雄的数组。
在 HTTP 教程中,你将会调用 HttpClient.get<Hero[]>()
它也一样返回一个 Observable<Hero[]>
,它也会发出单个值,这个值就是来自 HTTP 响应体中的英雄数组。
HeroesComponent
中订阅HeroService.getHeroes
方法以前返回一个 Hero[]
, 如今它返回的是 Observable<Hero[]>
。
你必须在 HeroesComponent
中也向本服务中的这种形式看齐。
找到 getHeroes
方法,而且把它替换为以下代码(和前一个版本对比显示):
heroes.component.ts (Observable)
|
heroes.component.ts (Original)
|
Observable.subscribe()
是关键的差别点。
上一个版本把英雄的数组赋值给了该组件的 heroes
属性。 这种赋值是同步的,这里包含的假设是服务器能当即返回英雄数组或者浏览器能在等待服务器响应时冻结界面。
当 HeroService
真的向远端服务器发起请求时,这种方式就行不通了。
新的版本等待 Observable
发出这个英雄数组,这可能当即发生,也可能会在几分钟以后。 而后,subscribe
函数把这个英雄数组传给这个回调函数,该函数把英雄数组赋值给组件的 heroes
属性。
使用这种异步方式,当 HeroService
从远端服务器获取英雄数据时,就能够工做了。
在这一节,你将
MessagesComponent
,它在屏幕的底部显示应用中的消息。MessageService
,用于发送要显示的消息。MessageService
注入到 HeroService
中。HeroService
成功获取了英雄数据时显示一条消息。MessagesComponent
使用 CLI 建立 MessagesComponent
。
|
CLI 在 src/app/
messages 中建立了组件文件,而且把 MessagesComponent
声明在了 AppModule
中。
修改 AppComponent
的模板来显示所生成的 MessagesComponent
:
/src/app/app.component.html
|
你能够在页面的底部看到来自的 MessagesComponent
的默认内容。
MessageService
使用 CLI 在 src/app
中建立 MessageService
。
|
打开MessageService
,并把它的内容改为这样:
/src/app/message.service.ts
|
该服务对外暴露了它的 messages 缓存,以及两个方法:add()
方法往缓存中添加一条消息,clear()
方法用于清空缓存。
HeroService
中从新打开 HeroService
,而且导入 MessageService
。
/src/app/hero.service.ts (import MessageService)
|
修改这个构造函数,添加一个私有的 messageService
属性参数。 Angular 将会在建立 HeroService
时把 MessageService
的单例注入到这个属性中。
|
这是一个典型的“服务中的服务”场景: 你把 MessageService
注入到了 HeroService
中,而 HeroService
又被注入到了 HeroesComponent
中。
HeroService
中发送一条消息修改 getHeroes
方法,在获取到英雄数组时发送一条消息。
|
HeroService
中显示消息MessagesComponent
能够显示全部消息, 包括当 HeroService
获取到英雄数据时发送的那条。
打开 MessagesComponent
,而且导入 MessageService
。
/src/app/messages/messages.component.ts (import MessageService)
|
修改构造函数,添加一个 public 的 messageService
属性。 Angular 将会在建立 MessagesComponent
的实例时 把 MessageService
的实例注入到这个属性中。
|
这个messageService
属性必须是公共属性,由于你将会在模板中绑定到它。
Angular 只会绑定到组件的公共属性。
MessageService
把 CLI 生成的 MessagesComponent
的模板改为这样:
src/app/messages/messages.component.html
|
这个模板直接绑定到了组件的 messageService
属性上。
*
ngIf 只有在有消息时才会显示消息区。*
ngFor 用来在一系列 <div>
元素中展现消息列表。click
事件绑定到了 MessageService.clear()
。当你把 最终代码 某一页的内容添加到 messages.component.css
中时,这些消息会变得好看一些。
刷新浏览器,页面显示出了英雄列表。 滚动到底部,就会在消息区看到来自 HeroService
的消息。 点击“清空”按钮,消息区不见了。
你的应用应该变成了这样 在线例子 / 下载范例。本页所说起的代码文件以下。
若是你想直接在 stackblitz 运行本页中的例子,请单击连接:https://stackblitz.com/github/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services
本页中所说起的代码以下:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services
对应的文件列表和代码连接以下:
HeroService
类中。HeroService
注册为该服务的提供商,以便在别处能够注入它。HeroService
中获取数据的方法提供了一个异步的函数签名。Observable
以及 RxJS 库。of()
方法返回了一个模拟英雄数据的可观察对象 (Observable<Hero[]>
)。ngOnInit
生命周期钩子中调用 HeroService
方法,而不是构造函数中。MessageService
,以便在类之间实现松耦合通信。HeroService
连同注入到它的服务 MessageService
一块儿,注入到了组件中。