AngularDart4.0 英雄之旅-教程-06服務

隨着「英雄之旅」應用的發展,您將添加更多須要訪問英雄數據的組件。css

不是一遍又一遍複製和粘貼相同的代碼,而是建立一個可重用的數據服務,並將其注入到須要它的組件中。 使用單獨的服務可以使組件保持精簡併專一於支持視圖,並使用模擬服務對組件進行單元測試變得容易。html

由於數據服務老是異步的,因此您將使用數據服務的基於Future的版原本完成頁面。java

當你完成這個頁面,應用程序應該看起來像這個實例(查看源代碼)。git

你開始的地方

在繼續英雄之旅以前,請確認您具備如下結構。 若是沒有,請返回前面的頁面。github

若是該應用程序還沒有運行,請啓動該應用程序。 在進行更改時,請經過從新加載瀏覽器窗口來保持運行。web

建立一個英雄服務

利益相關者但願以不一樣的頁面以各類方式展現英雄。 用戶能夠從列表中選擇一個英雄。 不久,您將添加一個儀表板與頂尖的表演英雄,並建立一個單獨的視圖編輯英雄的細節。 全部三個視圖都須要英雄數據。編程

目前,AppComponent定義了模擬英雄的顯示。 然而,定義英雄不是組件的工做,你不能輕易與其餘組件和視圖共享英雄名單。 在這個頁面中,您將把英雄數據採集業務轉移到一個提供數據的服務中,並與須要數據的全部組件共享該服務。api

建立一個可注入的HeroService

在lib / src下建立文件hero_service.dart瀏覽器

服務文件的命名約定是小寫的服務名稱,後跟_service。 對於多詞服務名稱,請使用小寫的snake_case。 例如,SpecialSuperHeroService的文件名是special_super_hero_service.dart緩存

 命名類HeroServicelib/src/hero_service.dart (empty class)

import 'package:angular/angular.dart';

@Injectable()
class HeroService {
}

注意你使用了@Injectable()註解。 這告訴Angular編譯器,HeroService將成爲注入的候選者(更多關於這個)。

獲取英雄數據

HeroService能夠從任何地方(Web服務,本地存儲或模擬數據源)獲取英雄數據。 如今,導入HeromockHeroes,並從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屬性外什麼也不作。 _heroServiceHeroService類型將構造函數的參數標識爲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()方法更新HeroServicelib/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(),看看應用程序的行爲。

下一節

相關文章
相關標籤/搜索