Angular 服務

英雄指南的 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

  1. HeroService 中,它會使用該服務發送消息。
  2. MessagesComponent 中,它會顯示其中的消息。

建立 HeroService

使用 Angular CLI 建立一個名叫 hero 的服務。數組

ng generate service hero瀏覽器

該命令會在src/app/hero.service.ts中生成HeroService類的骨架。HeroService類的代碼以下:緩存

src/app/hero.service.ts (new service)服務器

import { Injectable } from '@angular/core';

 

@Injectable({

  providedIn: 'root',

})

export class HeroService {

 

  constructor() { }

 

}

@Injectable() 服務

注意,這個新的服務導入了 Angular 的 Injectable 符號,而且給這個服務類添加了 @Injectable() 裝飾器。 它把這個類標記爲依賴注入系統的參與者之一。HeroService 類將會提供一個可注入的服務,而且它還能夠擁有本身的待注入的依賴。 目前它尚未依賴,可是很快就會有了

@Injectable() 裝飾器會接受該服務的元數據對象,就像 @Component() 對組件類的做用同樣。

獲取英雄數據

HeroService 能夠從任何地方獲取數據:Web 服務、本地存儲(LocalStorage)或一個模擬的數據源。

從組件中移除數據訪問邏輯,意味着未來任什麼時候候你均可以改變目前的實現方式,而不用改動任何組件。 這些組件不須要了解該服務的內部實現。

這節課中的實現仍然會提供模擬的英雄列表

導入 Hero 和 HEROES

import { Hero } from './hero';

import { HEROES } from './mock-heroes';

添加一個 getHeroes 方法,讓它返回模擬的英雄列表

getHeroes(): Hero[] {

  return HEROES;

}

提供(provide) HeroService

在要求 Angular 把 HeroService 注入到 HeroesComponent 以前,你必須先把這個服務提供給依賴注入系統稍後你就要這麼作。 你能夠經過註冊提供商來作到這一點。提供商用來建立和交付服務,在這個例子中,它會對 HeroService 類進行實例化,以提供該服務。

如今,你須要確保 HeroService 已經做爲該服務的提供商進行過註冊。 你要用一個注入器註冊它。注入器就是一個對象,負責在須要時選取和注入該提供商。

默認狀況下,Angular CLI 命令 ng generate service 會經過給 @Injectable 裝飾器添加元數據的形式,用根注入器將你的服務註冊成爲提供商。

若是你看看 HeroService 緊前面的 @Injectable() 語句定義,就會發現 providedIn 元數據的值是 'root':

@Injectable({

  providedIn: 'root',

})

@

Injectable

({
  providedIn: 'root',
})

當你在頂層提供該服務時,Angular 就會爲 HeroService 建立一個單一的、共享的實例,並把它注入到任何想要它的類上。 在 @Injectable 元數據中註冊該提供商,還能容許 Angular 經過移除那些徹底沒有用過的服務來進行優化。

要了解關於提供商的更多知識,參見提供商部分。 要了解關於注入器的更多知識,參見依賴注入指南

如今 HeroService 已經準備好插入到 HeroesComponent 中了。

這是一個過渡性的代碼範例,它將會容許你提供並使用 HeroService。此刻的代碼和最終代碼相差很大。

修改 HeroesComponent

打開 HeroesComponent 類文件。

刪除 HEROES 的導入語句,由於你之後不會再用它了。 轉而導入 HeroService

src/app/heroes/heroes.component.ts (import HeroService)

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

把 heroes 屬性的定義改成一句簡單的聲明。

heroes: Hero[];

注入 HeroService

往構造函數中添加一個私有的 heroService,其類型爲 HeroService

constructor(private heroService: HeroService) { }

這個參數同時作了兩件事:1. 聲明瞭一個私有 heroService 屬性,2. 把它標記爲一個 HeroService 的注入點。

當 Angular 建立 HeroesComponent 時,依賴注入系統就會把這個 heroService 參數設置爲 HeroService 的單例對象。

添加 getHeroes()

建立一個函數,以從服務中獲取這些英雄數據。

getHeroes(): void {

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

}

在 ngOnInit 中調用它

你當然能夠在構造函數中調用 getHeroes(),但那不是最佳實踐。

讓構造函數保持簡單,只作初始化操做,好比把構造函數的參數賦值給屬性。 構造函數不該該作任何事。 它固然不該該調用某個函數來向遠端服務(好比真實的數據服務)發起 HTTP 請求。

而是選擇在 ngOnInit 生命週期鉤子中調用 getHeroes(),以後交由 Angular 處理,它會在構造出 HeroesComponent 的實例以後的某個合適的時機調用 ngOnInit。

ngOnInit() {

  this.getHeroes();

}

查看運行效果

刷新瀏覽器,該應用仍運行的一如既往。 顯示英雄列表,而且當你點擊某個英雄的名字時顯示出英雄詳情視圖。

可觀察(Observable)的數據

HeroService.getHeroes() 的函數簽名是同步的,它所隱含的假設是 HeroService 老是能同步獲取英雄列表數據。 而 HeroesComponent 也一樣假設能同步取到 getHeroes() 的結果。

this.heroes = this.heroService.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)

import { Observable, of } from 'rxjs';

把 getHeroes 方法改爲這樣:

getHeroes(): Observable<Hero[]> {

  return of(HEROES);

}

of(HEROES)會返回一個Observable<Hero[]>,它會發出單個值,這個值就是這些模擬英雄的數組。

在 HTTP 教程中,你將會調用 HttpClient.get<Hero[]>() 它也一樣返回一個 Observable<Hero[]>,它也會發出單個值,這個值就是來自 HTTP 響應體中的英雄數組。

在 HeroesComponent 中訂閱

HeroService.getHeroes 方法以前返回一個 Hero[], 如今它返回的是 Observable<Hero[]>

你必須在 HeroesComponent 中也向本服務中的這種形式看齊。

找到 getHeroes 方法,而且把它替換爲以下代碼(和前一個版本對比顯示):

heroes.component.ts (Observable)

getHeroes(): void {

  this.heroService.getHeroes()

      .subscribe(heroes => this.heroes = heroes);

}

heroes.component.ts (Original)

getHeroes(): void {

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

}

Observable.subscribe() 是關鍵的差別點。

上一個版本把英雄的數組賦值給了該組件的 heroes 屬性。 這種賦值是同步的,這裏包含的假設是服務器能當即返回英雄數組或者瀏覽器能在等待服務器響應時凍結界面。

當 HeroService 真的向遠端服務器發起請求時,這種方式就行不通了。

新的版本等待 Observable 發出這個英雄數組,這可能當即發生,也可能會在幾分鐘以後。 而後,subscribe 函數把這個英雄數組傳給這個回調函數,該函數把英雄數組賦值給組件的 heroes屬性。

使用這種異步方式,當 HeroService 從遠端服務器獲取英雄數據時,就能夠工做了

顯示消息

在這一節,你將

  • 添加一個 MessagesComponent,它在屏幕的底部顯示應用中的消息。
  • 建立一個可注入的、全應用級別的 MessageService,用於發送要顯示的消息。
  • 把 MessageService 注入到 HeroService 中。
  • 當 HeroService 成功獲取了英雄數據時顯示一條消息。

建立 MessagesComponent

使用 CLI 建立 MessagesComponent

ng generate component messages

CLI 在 src/app/messages 中建立了組件文件,而且把 MessagesComponent 聲明在了 AppModule 中。

修改 AppComponent 的模板來顯示所生成的 MessagesComponent

/src/app/app.component.html

<h1>{{title}}</h1>

<app-heroes></app-heroes>

<app-messages></app-messages>

你能夠在頁面的底部看到來自的 MessagesComponent 的默認內容。

建立 MessageService

使用 CLI 在 src/app 中建立 MessageService

ng generate service message

打開MessageService,並把它的內容改爲這樣:

/src/app/message.service.ts

import { Injectable } from '@angular/core';

 

@Injectable({

  providedIn: 'root',

})

export class MessageService {

  messages: string[] = [];

 

  add(message: string) {

    this.messages.push(message);

  }

 

  clear() {

    this.messages = [];

  }

}

該服務對外暴露了它的 messages 緩存,以及兩個方法:add() 方法往緩存中添加一條消息,clear() 方法用於清空緩存。

把它注入到 HeroService 中

從新打開 HeroService,而且導入 MessageService

/src/app/hero.service.ts (import MessageService)

import { MessageService } from './message.service';

修改這個構造函數,添加一個私有的 messageService 屬性參數。 Angular 將會在建立 HeroService 時把 MessageService 的單例注入到這個屬性中。

constructor(private messageService: MessageService) { }

這是一個典型的「服務中的服務」場景: 你把 MessageService 注入到了 HeroService 中,而 HeroService 又被注入到了 HeroesComponent 中。

從 HeroService 中發送一條消息

修改 getHeroes 方法,在獲取到英雄數組時發送一條消息。

getHeroes(): Observable<Hero[]> {

  // TODO: send the message _after_ fetching the heroes

  this.messageService.add('HeroService: fetched heroes');

  return of(HEROES);

}

從 HeroService 中顯示消息

MessagesComponent 能夠顯示全部消息, 包括當 HeroService 獲取到英雄數據時發送的那條。

打開 MessagesComponent,而且導入 MessageService

/src/app/messages/messages.component.ts (import MessageService)

import { MessageService } from '../message.service';

修改構造函數,添加一個 public 的 messageService 屬性。 Angular 將會在建立 MessagesComponent 的實例時 把 MessageService 的實例注入到這個屬性中。

constructor(public messageService: MessageService) {}

這個messageService屬性必須是公共屬性,由於你將會在模板中綁定到它。

Angular 只會綁定到組件的公共屬性。

綁定到 MessageService

把 CLI 生成的 MessagesComponent 的模板改爲這樣:

src/app/messages/messages.component.html

<div *ngIf="messageService.messages.length">

 

  <h2>Messages</h2>

  <button class="clear"

          (click)="messageService.clear()">clear</button>

  <div *ngFor='let message of messageService.messages'> {{message}} </div>

 

</div>

這個模板直接綁定到了組件的 messageService 屬性上。

  • *ngIf 只有在有消息時纔會顯示消息區。
  • *ngFor 用來在一系列 <div> 元素中展現消息列表。
  • Angular 的事件綁定把按鈕的 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

對應的文件列表和代碼連接以下:

文件名

源代碼

src/app/hero.service.ts https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/hero.service.ts
src/app/heroes/heroes.component.ts https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/heroes/heroes.component.ts
src/app/messages/messages.component.ts https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.ts
src/app/messages/messages.component.html https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.html
src/app/messages/messages.component.css https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/messages/messages.component.css
src/app/app.module.ts https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/app.module.ts
src/app/app.component.html https://github.com/cwiki-us-angular/cwiki-us-angular-tour-of-hero-services/blob/master/src/app/app.component.html

小結

  • 你把數據訪問邏輯重構到了 HeroService 類中。
  • 你在根注入器中把 HeroService 註冊爲該服務的提供商,以便在別處能夠注入它。
  • 你使用 Angular 依賴注入機制把它注入到了組件中。
  • 你給 HeroService 中獲取數據的方法提供了一個異步的函數簽名。
  • 你發現了 Observable 以及 RxJS 庫。
  • 你使用 RxJS 的 of() 方法返回了一個模擬英雄數據的可觀察對象 (Observable<Hero[]>)。
  • 在組件的 ngOnInit 生命週期鉤子中調用 HeroService 方法,而不是構造函數中。
  • 你建立了一個 MessageService,以便在類之間實現鬆耦合通信。
  • HeroService 連同注入到它的服務 MessageService 一塊兒,注入到了組件中。

 

https://www.cwiki.us/display/AngularZH/Services

相關文章
相關標籤/搜索