祖庭海 發表於 2019-5-31 05:45:00

Angular 服务

<p>英雄指南的&nbsp;<code>HeroesComponent</code>&nbsp;目前获取和显示的都是模拟数据。</p>
<p>本节课的重构完成之后,<code>HeroesComponent</code>&nbsp;变得更精简,并且聚焦于为它的视图提供支持。这也让它更容易使用模拟服务进行单元测试。</p>
<p>如果你希望从 GitHub 上查看我们提供测试的源代码,你可以访问下面的链接:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services</p>
<h2 id="Services-为什么需要服务">为什么需要服务</h2>
<p>组件不应该直接获取或保存数据,它们不应该了解是否在展示假数据。 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。</p>
<p>本节课,你将创建一个&nbsp;<code>HeroService</code>,应用中的所有类都可以使用它来获取英雄列表。 不要使用&nbsp;<code>new</code>&nbsp;来创建此服务,而要依靠 Angular 的<em>依赖注入</em>机制把它注入到&nbsp;<code>HeroesComponent</code>&nbsp;的构造函数中。</p>
<p>服务是在多个“互相不知道”的类之间共享信息的好办法。 你将创建一个&nbsp;<code>MessageService</code>,并且把它注入到两个地方:</p>
<ol>
<li><code>HeroService</code>&nbsp;中,它会使用该服务发送消息。</li>
<li><code>MessagesComponent</code>&nbsp;中,它会显示其中的消息。</li>
</ol>
<h2 id="Services-创建HeroService">创建&nbsp;<code>HeroService</code></h2>
<p>使用 Angular CLI 创建一个名叫&nbsp;<code>hero</code>&nbsp;的服务。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_442114" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">ng generate service hero</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>该命令会在<code>src/app/hero.service.ts</code>中生成<code>HeroService</code>类的骨架。<code>HeroService</code>类的代码如下:</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>src/app/hero.service.ts (new service)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_96261" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ Injectable } from&nbsp;</code><code class="java string">'@angular/core'</code><code class="java plain">;</code></div>
<div class="line number2 index1 alt1">&nbsp;</div>
<div class="line number3 index2 alt2"><code class="java color1">@Injectable</code><code class="java plain">({</code></div>
<div class="line number4 index3 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">providedIn:&nbsp;</code><code class="java string">'root'</code><code class="java plain">,</code></div>
<div class="line number5 index4 alt2"><code class="java plain">})</code></div>
<div class="line number6 index5 alt1"><code class="java plain">export&nbsp;</code><code class="java keyword">class</code>&nbsp;<code class="java plain">HeroService {</code></div>
<div class="line number7 index6 alt2">&nbsp;</div>
<div class="line number8 index7 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">constructor() { }</code></div>
<div class="line number9 index8 alt2">&nbsp;</div>
<div class="line number10 index9 alt1"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h3 id="Services-@Injectable()服务"><em>@Injectable()</em>&nbsp;服务</h3>
<p>注意,这个新的服务导入了 Angular 的&nbsp;<code></code>Injectable&nbsp;符号,并且给这个服务类添加了&nbsp;<code>@</code>Injectable<code>()</code>&nbsp;装饰器。 它把这个类标记为<em>依赖注入系统</em>的参与者之一。<code>HeroService</code>&nbsp;类将会提供一个可注入的服务,并且它还可以拥有自己的待注入的依赖。 目前它还没有依赖,但是很快就会有了。</p>
<p><code>@</code>Injectable<code>()</code>&nbsp;装饰器会接受该服务的元数据对象,就像&nbsp;<code>@</code>Component<code>()</code>&nbsp;对组件类的作用一样。</p>
<h3 id="Services-获取英雄数据">获取英雄数据</h3>
<p><code>HeroService</code>&nbsp;可以从任何地方获取数据:Web 服务、本地存储(LocalStorage)或一个模拟的数据源。</p>
<p>从组件中移除数据访问逻辑,意味着将来任何时候你都可以改变目前的实现方式,而不用改动任何组件。 这些组件不需要了解该服务的内部实现。</p>
<p>这节课中的实现仍然会提供<em>模拟的英雄列表</em>。</p>
<p>导入&nbsp;<code>Hero</code>&nbsp;和&nbsp;<code>HEROES</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_836518" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ Hero } from&nbsp;</code><code class="java string">'./hero'</code><code class="java plain">;</code></div>
<div class="line number2 index1 alt1"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ HEROES } from&nbsp;</code><code class="java string">'./mock-heroes'</code><code class="java plain">;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>添加一个&nbsp;<code>getHeroes</code>&nbsp;方法,让它返回<em>模拟的英雄列表</em>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_524182" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">getHeroes(): Hero[] {</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">return</code>&nbsp;<code class="java plain">HEROES;</code></div>
<div class="line number3 index2 alt2"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h2 id="Services-提供(provide)HeroService">提供(provide)&nbsp;<code>HeroService</code></h2>
<p>在要求 Angular 把&nbsp;<code>HeroService</code>&nbsp;注入到&nbsp;<code>HeroesComponent</code>&nbsp;之前,你必须先把这个服务<em>提供给依赖注入系统</em>。稍后你就要这么做。 你可以通过注册<em>提供商</em>来做到这一点。提供商用来创建和交付服务,在这个例子中,它会对&nbsp;<code>HeroService</code>&nbsp;类进行实例化,以提供该服务。</p>
<p>现在,你需要确保&nbsp;<code>HeroService</code>&nbsp;已经作为该服务的提供商进行过注册。 你要用一个<em>注入器</em>注册它。注入器就是一个对象,负责在需要时选取和注入该提供商。</p>
<p>默认情况下,Angular CLI 命令&nbsp;<code>ng generate service</code>&nbsp;会通过给&nbsp;<code>@</code>Injectable&nbsp;装饰器添加元数据的形式,用<em>根注入器</em>将你的服务注册成为提供商。</p>
<p>如果你看看&nbsp;<code>HeroService</code>&nbsp;紧前面的&nbsp;<code>@</code>Injectable<code>()</code>&nbsp;语句定义,就会发现&nbsp;<code></code>providedIn&nbsp;元数据的值是 'root':</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_602869" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java color1">@Injectable</code><code class="java plain">({</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">providedIn:&nbsp;</code><code class="java string">'root'</code><code class="java plain">,</code></div>
<div class="line number3 index2 alt2"><code class="java plain">})</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<pre class="prettyprint lang-"><code class="animated fadeIn">@</code></pre>
<p><span class="lit">Injectable</span></p>
<pre class="prettyprint lang-"><code class="animated fadeIn"><span class="pun">({<span class="pln">
providedIn<span class="pun">: <span class="str">'root'<span class="pun">,
<span class="pun">})</span></span></span></span></span></span></code></pre>
<p>当你在顶层提供该服务时,Angular 就会为&nbsp;<code>HeroService</code>&nbsp;创建一个单一的、共享的实例,并把它注入到任何想要它的类上。 在&nbsp;<code>@</code>Injectable&nbsp;元数据中注册该提供商,还能允许 Angular 通过移除那些完全没有用过的服务来进行优化。</p>
<div class="confluence-information-macro confluence-information-macro-information conf-macro output-block" data-hasbody="true" data-macro-name="info">
<div class="confluence-information-macro-body">
<p>要了解关于提供商的更多知识,参见提供商部分。 要了解关于注入器的更多知识,参见依赖注入指南。</p>
</div>
</div>
<p>现在&nbsp;<code>HeroService</code>&nbsp;已经准备好插入到&nbsp;<code>HeroesComponent</code>&nbsp;中了。</p>
<div class="confluence-information-macro confluence-information-macro-note conf-macro output-block" data-hasbody="true" data-macro-name="note">
<div class="confluence-information-macro-body">
<p>这是一个过渡性的代码范例,它将会允许你提供并使用&nbsp;<code>HeroService</code>。此刻的代码和最终代码相差很大。</p>
</div>
</div>
<h2 id="Services-修改HeroesComponent">修改&nbsp;<code>HeroesComponent</code></h2>
<p>打开&nbsp;<code>HeroesComponent</code>&nbsp;类文件。</p>
<p>删除&nbsp;<code>HEROES</code>&nbsp;的导入语句,因为你以后不会再用它了。 转而导入&nbsp;<code>HeroService</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>src/app/heroes/heroes.component.ts (import HeroService)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_563422" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ HeroService } from&nbsp;</code><code class="java string">'../hero.service'</code><code class="java plain">;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>把&nbsp;<code>heroes</code>&nbsp;属性的定义改为一句简单的声明。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_133758" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">heroes: Hero[];</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h3 id="Services-注入HeroService">注入&nbsp;<code>HeroService</code></h3>
<p>往构造函数中添加一个私有的&nbsp;<code>heroService</code>,其类型为&nbsp;<code>HeroService</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_606313" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">constructor(</code><code class="java keyword">private</code>&nbsp;<code class="java plain">heroService: HeroService) { }</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>这个参数同时做了两件事:1. 声明了一个私有&nbsp;<code>heroService</code>&nbsp;属性,2. 把它标记为一个&nbsp;<code>HeroService</code>&nbsp;的注入点。</p>
<p>当 Angular 创建&nbsp;<code>HeroesComponent</code>&nbsp;时,依赖注入系统就会把这个&nbsp;<code>heroService</code>&nbsp;参数设置为&nbsp;<code>HeroService</code>&nbsp;的单例对象。</p>
<h3 id="Services-添加getHeroes()">添加&nbsp;<em>getHeroes()</em></h3>
<p>创建一个函数,以从服务中获取这些英雄数据。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_927160" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">getHeroes():&nbsp;</code><code class="java keyword">void</code>&nbsp;<code class="java plain">{</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroes =&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroService.getHeroes();</code></div>
<div class="line number3 index2 alt2"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h3 id="Services-在ngOnInit中调用它">在&nbsp;<code>ngOnInit</code>&nbsp;中调用它</h3>
<p>你固然可以在构造函数中调用&nbsp;<code>getHeroes()</code>,但那不是最佳实践。</p>
<p>让构造函数保持简单,只做初始化操作,比如把构造函数的参数赋值给属性。 构造函数不应该<em>做任何事</em>。 它当然不应该调用某个函数来向远端服务(比如真实的数据服务)发起 HTTP 请求。</p>
<p>而是选择在 ngOnInit 生命周期钩子中调用 getHeroes(),之后交由 Angular 处理,它会在构造出 HeroesComponent 的实例之后的某个合适的时机调用 ngOnInit。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_914580" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">ngOnInit() {</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.getHeroes();</code></div>
<div class="line number3 index2 alt2"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h3 id="Services-查看运行效果">查看运行效果</h3>
<p>刷新浏览器,该应用仍运行的一如既往。 显示英雄列表,并且当你点击某个英雄的名字时显示出英雄详情视图。</p>
<h2 id="Services-可观察(Observable)的数据">可观察(Observable)的数据</h2>
<p><code>HeroService.getHeroes()</code>&nbsp;的函数签名是<em>同步的</em>,它所隐含的假设是&nbsp;<code>HeroService</code>&nbsp;总是能同步获取英雄列表数据。 而&nbsp;<code>HeroesComponent</code>&nbsp;也同样假设能同步取到&nbsp;<code>getHeroes()</code>&nbsp;的结果。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_565692" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">this</code><code class="java plain">.heroes =&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroService.getHeroes();</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>这在真实的应用中几乎是不可能的。 现在能这么做,只是因为目前该服务返回的是<em>模拟数据</em>。 不过很快,该应用就要从远端服务器获取英雄数据了,而那天生就是<em>异步</em>操作。</p>
<p><code>HeroService</code>&nbsp;必须等服务器给出响应, 而&nbsp;<code>getHeroes()</code>&nbsp;不能立即返回英雄数据, 浏览器也不会在该服务等待期间停止响应。</p>
<p><code>HeroService.getHeroes()</code>&nbsp;必须具有某种形式的<em>异步函数签名</em>。</p>
<p>它可以使用回调函数,可以返回&nbsp;<code>Promise</code>(承诺),也可以返回&nbsp;<code>Observable</code>(可观察对象)。</p>
<p>这节课,<code>HeroService.getHeroes()</code>&nbsp;将会返回&nbsp;<code>Observable</code>,因为它最终会使用 Angular 的&nbsp;<code>HttpClient.get</code>&nbsp;方法来获取英雄数据,而&nbsp;<code>HttpClient.get()</code>&nbsp;会返回&nbsp;<code>Observable</code>。</p>
<h3 id="Services-可观察对象版本的HeroService">可观察对象版本的&nbsp;<code>HeroService</code></h3>
<p><code>Observable</code>&nbsp;是&nbsp;RxJS 库中的一个关键类。</p>
<p>在稍后的 HTTP 教程中,你就会知道 Angular&nbsp;<code></code>HttpClient&nbsp;的方法会返回 RxJS 的&nbsp;<code>Observable</code>。 这节课,你将使用 RxJS 的&nbsp;<code>of()</code>&nbsp;函数来模拟从服务器返回数据。</p>
<p>打开&nbsp;<code>HeroService</code>&nbsp;文件,并从 RxJS 中导入&nbsp;<code>Observable</code>&nbsp;和&nbsp;<code>of</code>&nbsp;符号。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>src/app/hero.service.ts (Observable imports)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_774790" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ Observable, of } from&nbsp;</code><code class="java string">'rxjs'</code><code class="java plain">;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>把&nbsp;<code>getHeroes</code>&nbsp;方法改成这样:</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_997310" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">getHeroes(): Observable&lt;Hero[]&gt; {</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">return</code>&nbsp;<code class="java plain">of(HEROES);</code></div>
<div class="line number3 index2 alt2"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p><code>of(HEROES)</code>会返回一个<code>Observable&lt;Hero[]&gt;</code>,它会发出单个值,这个值就是这些模拟英雄的数组。</p>
<div class="confluence-information-macro confluence-information-macro-information conf-macro output-block" data-hasbody="true" data-macro-name="info">
<div class="confluence-information-macro-body">
<p>在&nbsp;HTTP 教程中,你将会调用&nbsp;<code>HttpClient.get&lt;Hero[]&gt;()</code>&nbsp;它也同样返回一个&nbsp;<code>Observable&lt;Hero[]&gt;</code>,它也会发出单个值,这个值就是来自 HTTP 响应体中的英雄数组。</p>
</div>
</div>
<h3 id="Services-在HeroesComponent中订阅">在&nbsp;<code>HeroesComponent</code>&nbsp;中订阅</h3>
<p><code>HeroService.getHeroes</code>&nbsp;方法之前返回一个&nbsp;<code>Hero[]</code>, 现在它返回的是&nbsp;<code>Observable&lt;Hero[]&gt;</code>。</p>
<p>你必须在&nbsp;<code>HeroesComponent</code>&nbsp;中也向本服务中的这种形式看齐。</p>
<p>找到&nbsp;<code>getHeroes</code>&nbsp;方法,并且把它替换为如下代码(和前一个版本对比显示):</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>heroes.component.ts (Observable)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_442874" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">getHeroes():&nbsp;</code><code class="java keyword">void</code>&nbsp;<code class="java plain">{</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroService.getHeroes()</code></div>
<div class="line number3 index2 alt2"><code class="java spaces">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="java plain">.subscribe(heroes =&gt;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroes = heroes);</code></div>
<div class="line number4 index3 alt1"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>heroes.component.ts (Original)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_525872" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">getHeroes():&nbsp;</code><code class="java keyword">void</code>&nbsp;<code class="java plain">{</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroes =&nbsp;</code><code class="java keyword">this</code><code class="java plain">.heroService.getHeroes();</code></div>
<div class="line number3 index2 alt2"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p><code>Observable.subscribe()</code>&nbsp;是关键的差异点。</p>
<p>上一个版本把英雄的数组赋值给了该组件的&nbsp;<code>heroes</code>&nbsp;属性。 这种赋值是<em>同步</em>的,这里包含的假设是服务器能立即返回英雄数组或者浏览器能在等待服务器响应时冻结界面。</p>
<p>当&nbsp;<code>HeroService</code>&nbsp;真的向远端服务器发起请求时,这种方式就行不通了。</p>
<p>新的版本等待&nbsp;<code>Observable</code>&nbsp;发出这个英雄数组,这可能立即发生,也可能会在几分钟之后。 然后,<code>subscribe</code>&nbsp;函数把这个英雄数组传给这个回调函数,该函数把英雄数组赋值给组件的&nbsp;<code>heroes</code>属性。</p>
<p>使用这种异步方式,当&nbsp;<code>HeroService</code>&nbsp;从远端服务器获取英雄数据时,就<em>可以工作了</em>。</p>
<h2 id="Services-显示消息">显示消息</h2>
<p>在这一节,你将</p>
<ul>
<li>添加一个&nbsp;<code>MessagesComponent</code>,它在屏幕的底部显示应用中的消息。</li>
<li>创建一个可注入的、全应用级别的&nbsp;<code>MessageService</code>,用于发送要显示的消息。</li>
<li>把&nbsp;<code>MessageService</code>&nbsp;注入到&nbsp;<code>HeroService</code>&nbsp;中。</li>
<li>当&nbsp;<code>HeroService</code>&nbsp;成功获取了英雄数据时显示一条消息。</li>
</ul>
<h3 id="Services-创建MessagesComponent">创建&nbsp;<code>MessagesComponent</code></h3>
<p>使用 CLI 创建&nbsp;<code>MessagesComponent</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_914416" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">ng generate component messages</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>CLI 在&nbsp;<code>src/app/</code>messages&nbsp;中创建了组件文件,并且把&nbsp;<code>MessagesComponent</code>&nbsp;声明在了&nbsp;<code>AppModule</code>&nbsp;中。</p>
<p>修改&nbsp;<code>AppComponent</code>&nbsp;的模板来显示所生成的&nbsp;<code>MessagesComponent</code>:</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>/src/app/app.component.html</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_835194" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">&lt;h1&gt;{{title}}&lt;/h1&gt;</code></div>
<div class="line number2 index1 alt1"><code class="java plain">&lt;app-heroes&gt;&lt;/app-heroes&gt;</code></div>
<div class="line number3 index2 alt2"><code class="java plain">&lt;app-messages&gt;&lt;/app-messages&gt;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>你可以在页面的底部看到来自的&nbsp;<code>MessagesComponent</code>&nbsp;的默认内容。</p>
<h3 id="Services-创建MessageService">创建&nbsp;<code>MessageService</code></h3>
<p>使用 CLI 在&nbsp;<code>src/app</code>&nbsp;中创建&nbsp;<code>MessageService</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_376836" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">ng generate service message</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>打开<code>MessageService</code>,并把它的内容改成这样:</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>/src/app/message.service.ts</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_899971" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ Injectable } from&nbsp;</code><code class="java string">'@angular/core'</code><code class="java plain">;</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;</code></div>
<div class="line number3 index2 alt2"><code class="java color1">@Injectable</code><code class="java plain">({</code></div>
<div class="line number4 index3 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">providedIn:&nbsp;</code><code class="java string">'root'</code><code class="java plain">,</code></div>
<div class="line number5 index4 alt2"><code class="java plain">})</code></div>
<div class="line number6 index5 alt1"><code class="java plain">export&nbsp;</code><code class="java keyword">class</code>&nbsp;<code class="java plain">MessageService {</code></div>
<div class="line number7 index6 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">messages: string[] = [];</code></div>
<div class="line number8 index7 alt1"><code class="java spaces">&nbsp;</code></div>
<div class="line number9 index8 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">add(message: string) {</code></div>
<div class="line number10 index9 alt1"><code class="java spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.messages.push(message);</code></div>
<div class="line number11 index10 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">}</code></div>
<div class="line number12 index11 alt1"><code class="java spaces">&nbsp;</code></div>
<div class="line number13 index12 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">clear() {</code></div>
<div class="line number14 index13 alt1"><code class="java spaces">&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.messages = [];</code></div>
<div class="line number15 index14 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">}</code></div>
<div class="line number16 index15 alt1"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>该服务对外暴露了它的&nbsp;<code></code>messages&nbsp;缓存,以及两个方法:<code>add()</code>&nbsp;方法往缓存中添加一条消息,<code>clear()</code>&nbsp;方法用于清空缓存。</p>
<h3 id="Services-把它注入到HeroService中">把它注入到&nbsp;<code>HeroService</code>&nbsp;中</h3>
<p>重新打开&nbsp;<code>HeroService</code>,并且导入&nbsp;<code>MessageService</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>/src/app/hero.service.ts (import MessageService)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_628523" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ MessageService } from&nbsp;</code><code class="java string">'./message.service'</code><code class="java plain">;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>修改这个构造函数,添加一个私有的&nbsp;<code>messageService</code>&nbsp;属性参数。 Angular 将会在创建&nbsp;<code>HeroService</code>&nbsp;时把&nbsp;<code>MessageService</code>&nbsp;的单例注入到这个属性中。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_493661" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">constructor(</code><code class="java keyword">private</code>&nbsp;<code class="java plain">messageService: MessageService) { }</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="confluence-information-macro confluence-information-macro-information conf-macro output-block" data-hasbody="true" data-macro-name="info">
<div class="confluence-information-macro-body">
<p>这是一个典型的“服务中的服务”场景: 你把&nbsp;<code>MessageService</code>&nbsp;注入到了&nbsp;<code>HeroService</code>&nbsp;中,而&nbsp;<code>HeroService</code>&nbsp;又被注入到了&nbsp;<code>HeroesComponent</code>&nbsp;中。</p>
</div>
</div>
<h3 id="Services-从HeroService中发送一条消息">从&nbsp;<code>HeroService</code>&nbsp;中发送一条消息</h3>
<p>修改&nbsp;<code>getHeroes</code>&nbsp;方法,在获取到英雄数组时发送一条消息。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_73444" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">getHeroes(): Observable&lt;Hero[]&gt; {</code></div>
<div class="line number2 index1 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java comments">// TODO: send the message _after_ fetching the heroes</code></div>
<div class="line number3 index2 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">this</code><code class="java plain">.messageService.add(</code><code class="java string">'HeroService: fetched heroes'</code><code class="java plain">);</code></div>
<div class="line number4 index3 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java keyword">return</code>&nbsp;<code class="java plain">of(HEROES);</code></div>
<div class="line number5 index4 alt2"><code class="java plain">}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<h3 id="Services-从HeroService中显示消息">从&nbsp;<code>HeroService</code>&nbsp;中显示消息</h3>
<p><code>MessagesComponent</code>&nbsp;可以显示所有消息, 包括当&nbsp;<code>HeroService</code>&nbsp;获取到英雄数据时发送的那条。</p>
<p>打开&nbsp;<code>MessagesComponent</code>,并且导入&nbsp;<code>MessageService</code>。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>/src/app/messages/messages.component.ts (import MessageService)</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_410357" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java keyword">import</code>&nbsp;<code class="java plain">{ MessageService } from&nbsp;</code><code class="java string">'../message.service'</code><code class="java plain">;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>修改构造函数,添加一个&nbsp;<strong>public</strong>&nbsp;的&nbsp;<code>messageService</code>&nbsp;属性。 Angular 将会在创建&nbsp;<code>MessagesComponent</code>&nbsp;的实例时 把&nbsp;<code>MessageService</code>&nbsp;的实例注入到这个属性中。</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeContent panelContent pdl">
<div id="highlighter_859155" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">constructor(</code><code class="java keyword">public</code>&nbsp;<code class="java plain">messageService: MessageService) {}</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>这个<code>messageService</code>属性必须是公共属性,因为你将会在模板中绑定到它。</p>
<div class="confluence-information-macro confluence-information-macro-note conf-macro output-block" data-hasbody="true" data-macro-name="note">
<div class="confluence-information-macro-body">
<p>Angular 只会绑定到组件的<em>公共</em>属性。</p>
</div>
</div>
<h3 id="Services-绑定到MessageService">绑定到&nbsp;<code>MessageService</code></h3>
<p>把 CLI 生成的&nbsp;<code>MessagesComponent</code>&nbsp;的模板改成这样:</p>
<div class="code panel pdl conf-macro output-block" data-hasbody="true" data-macro-name="code">
<div class="codeHeader panelHeader pdl"><strong>src/app/messages/messages.component.html</strong></div>
<div class="codeContent panelContent pdl">
<div id="highlighter_497140" class="syntaxhighlighter sh-eclipse nogutter java">
<table border="0" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="code">
<div class="container" title="Hint: double-click to select code">
<div class="line number1 index0 alt2"><code class="java plain">&lt;div *ngIf=</code><code class="java string">"messageService.messages.length"</code><code class="java plain">&gt;</code></div>
<div class="line number2 index1 alt1">&nbsp;</div>
<div class="line number3 index2 alt2"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">&lt;h2&gt;Messages&lt;/h2&gt;</code></div>
<div class="line number4 index3 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">&lt;button&nbsp;</code><code class="java keyword">class</code><code class="java plain">=</code><code class="java string">"clear"</code></div>
<div class="line number5 index4 alt2"><code class="java spaces">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</code><code class="java plain">(click)=</code><code class="java string">"messageService.clear()"</code><code class="java plain">&gt;clear&lt;/button&gt;</code></div>
<div class="line number6 index5 alt1"><code class="java spaces">&nbsp;&nbsp;</code><code class="java plain">&lt;div *ngFor=</code><code class="java string">'let message of messageService.messages'</code><code class="java plain">&gt; {{message}} &lt;/div&gt;</code></div>
<div class="line number7 index6 alt2">&nbsp;</div>
<div class="line number8 index7 alt1"><code class="java plain">&lt;/div&gt;</code></div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<p>这个模板直接绑定到了组件的&nbsp;<code>messageService</code>&nbsp;属性上。</p>
<ul>
<li><code>*</code>ngIf&nbsp;只有在有消息时才会显示消息区。</li>
<li><code>*</code>ngFor&nbsp;用来在一系列&nbsp;<code>&lt;div&gt;</code>&nbsp;元素中展示消息列表。</li>
<li>Angular 的事件绑定把按钮的&nbsp;<code>click</code>&nbsp;事件绑定到了&nbsp;<code>MessageService.clear()</code>。</li>
</ul>
<p>当你把&nbsp;最终代码&nbsp;某一页的内容添加到&nbsp;<code>messages.component.css</code>&nbsp;中时,这些消息会变得好看一些。</p>
<p>刷新浏览器,页面显示出了英雄列表。 滚动到底部,就会在消息区看到来自&nbsp;<code>HeroService</code>&nbsp;的消息。 点击“清空”按钮,消息区不见了。</p>
<h2 id="Services-查看最终代码">查看最终代码</h2>
<p>你的应用应该变成了这样&nbsp;<span class="ng-star-inserted">在线例子&nbsp;/&nbsp;下载范例。本页所提及的代码文件如下。</span></p>
<p>如果你想直接在&nbsp;stackblitz 运行本页中的例子,请单击链接:https://stackblitz.com/github/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services</p>
<p>本页中所提及的代码如下:https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services</p>
<p>对应的文件列表和代码链接如下:</p>
<div class="table-wrap">
<table class="confluenceTable tablesorter tablesorter-default"><colgroup> <col> <col></colgroup>
<thead>
<tr class="tablesorter-headerRow"><th class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" scope="col" data-column="0">
<div class="tablesorter-header-inner">
<p>文件名</p>
</div>
</th><th class="confluenceTh tablesorter-header sortableHeader tablesorter-headerUnSorted" scope="col" data-column="1">
<div class="tablesorter-header-inner">
<p>源代码</p>
</div>
</th></tr>
</thead>
<tbody>
<tr>
<td class="confluenceTd">src/app/hero.service.ts</td>
<td class="confluenceTd">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/hero.service.ts</td>
</tr>
<tr>
<td class="confluenceTd" colspan="1">src/app/heroes/heroes.component.ts</td>
<td class="confluenceTd" colspan="1">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/heroes/heroes.component.ts</td>
</tr>
<tr>
<td class="confluenceTd" colspan="1">src/app/messages/messages.component.ts</td>
<td class="confluenceTd" colspan="1">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.ts</td>
</tr>
<tr>
<td class="confluenceTd" colspan="1">src/app/messages/messages.component.html</td>
<td class="confluenceTd" colspan="1">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.html</td>
</tr>
<tr>
<td class="confluenceTd" colspan="1">src/app/messages/messages.component.css</td>
<td class="confluenceTd" colspan="1">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.css</td>
</tr>
<tr>
<td class="confluenceTd" colspan="1">src/app/app.module.ts</td>
<td class="confluenceTd" colspan="1">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/app.module.ts</td>
</tr>
<tr>
<td class="confluenceTd" colspan="1">src/app/app.component.html</td>
<td class="confluenceTd" colspan="1">https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/app.component.html</td>
</tr>
</tbody>
</table>
</div>
<h2 id="Services-小结">小结</h2>
<ul>
<li>你把数据访问逻辑重构到了&nbsp;<code>HeroService</code>&nbsp;类中。</li>
<li>你在根注入器中把&nbsp;<code>HeroService</code>&nbsp;注册为该服务的提供商,以便在别处可以注入它。</li>
<li>你使用&nbsp;Angular 依赖注入机制把它注入到了组件中。</li>
<li>你给&nbsp;<code>HeroService</code>&nbsp;中获取数据的方法提供了一个异步的函数签名。</li>
<li>你发现了&nbsp;<code>Observable</code>&nbsp;以及 RxJS 库。</li>
<li>你使用 RxJS 的&nbsp;<code>of()</code>&nbsp;方法返回了一个模拟英雄数据的<em>可观察对象</em>&nbsp;(<code>Observable&lt;Hero[]&gt;</code>)。</li>
<li>在组件的&nbsp;<code>ngOnInit</code>&nbsp;生命周期钩子中调用&nbsp;<code>HeroService</code>&nbsp;方法,而不是构造函数中。</li>
<li>你创建了一个&nbsp;<code>MessageService</code>,以便在类之间实现松耦合通讯。</li>
<li><code>HeroService</code>&nbsp;连同注入到它的服务&nbsp;<code>MessageService</code>&nbsp;一起,注入到了组件中。</li>
</ul>
<p>&nbsp;</p>
<p>https://www.cwiki.us/display/AngularZH/Services</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/huyuchengus/p/10953101.html
頁: [1]
查看完整版本: Angular 服务