AngularDart 4.0 高級-HTTP 客戶端

大多數前端應用程序使用HTTP協議與後端服務進行通訊。 Dart網絡應用程序一般使用XMLHttpRequest(XHR)API執行此操做,使用dart:html庫中的HttpRequest或更高級別的API(例如http包提供的內容)。php

如下演示使用http軟件包來講明服務器通訊:html

試試主持兩個演示的實例(查看源代碼)。前端

提供HTTP服務

此頁的demo使用了http包的Client接口. 下面的代碼爲Client註冊了一個 factory provider (建立了一個 BrowserClient 實例) :java

 web/main.dart (v1)git

import 'package:angular/angular.dart';
import 'package:http/browser_client.dart';
import 'package:http/http.dart';
import 'package:server_communication/app_component.dart';

void main() {
  bootstrap(AppComponent, [
    provide(Client, useFactory: () => new BrowserClient(), deps: [])
  ]);
}

HTTP客戶端演示:英雄之旅

此demo是Tour of Heroes應用程序的縮小版本. 它從服務中接收heroes而且在列表中展現它們.用戶能夠添加一個新的Hero而且保存到服務端.github

下面是應用程序的UI:web

此demo有一個單獨的組件, HeroListComponent. 下面是它的模板:json

lib/src/toh/hero_list_component.htmlbootstrap

<h1>Tour of Heroes</h1>
<h3>Heroes:</h3>
<ul>
  <li *ngFor="let hero of heroes">{{hero.name}}</li>
</ul>

<label>New hero name: <input #newHeroName /></label>
<button (click)="addHero(newHeroName.value); newHeroName.value=''">Add Hero</button>

<p class="error" *ngIf="errorMessage != null">{{errorMessage}}</p>

模板的ngFor指令顯示heroes列表.列表下面是輸入框和Add Hero按鈕,容許用戶添加新的英雄.後端

一個模板引用變量, newHeroName, 賦予(click)事件綁定存取輸入框的值. 當用戶單擊按鈕時, 單擊處理程序傳遞輸入值到addHero()方法. 單擊處理程序清空輸入框.

按鈕下面是錯誤消息區域.

HeroListComponent 類

這是組件類:lib/src/toh/hero_list_component.dart (class)

class HeroListComponent implements OnInit {
  final HeroService _heroService;
  String errorMessage;
  List<Hero> heroes = [];

  HeroListComponent(this._heroService);

  Future<Null> ngOnInit() => getHeroes();

  Future<Null> getHeroes() async {
    try {
      heroes = await _heroService.getHeroes();
    } catch (e) {
      errorMessage = e.toString();
    }
  }

  Future<Null> addHero(String name) async {
    name = name.trim();
    if (name.isEmpty) return;
    try {
      heroes.add(await _heroService.create(name));
    } catch (e) {
      errorMessage = e.toString();
    }
  }
}

Angular 注入 HeroService 到構造器,組件調用服務提取和保存數據.

組件不直接與Client做用.替而代之,它委派數據到HeroService.

始終將數據訪問權委派給支持的服務類。

雖然 在運行時組件在建立以後當即請求heroes, 此請求 不在組件的構造器內. 替而代之,請求在ngOnInit生命週期鉤子.

保持構造器簡單。 當組件的構造器很簡單時,組件更容易測試和調試,而全部真正的工做(如調用遠程服務器)都是由單獨的方法處理的。

hero 服務中的異步方法, getHeroes() 和 create(), 返回Future值(當前英雄列表和最近添加的英雄), 各自地. 英雄列表組件中的方法, getHeroes() 和addHero(), 指定當異步方法調用成功或失敗時採起的操做.

關於Future的更多信息,查看 futures tutorial 資源在指導的最後.

獲取數據

在以前的示例中,應用經過返回服務中的模擬英雄來僞造與服務器的交互:

import 'dart:async';

import 'package:angular/angular.dart';

import 'hero.dart';
import 'mock_heroes.dart';

@Injectable()
class HeroService {
  Future<List<Hero>> getHeroes() async => mockHeroes;
}

是時候得到真實的數據了。 如下代碼使HeroService從服務器獲取英雄:

lib/src/toh/hero_service.dart (revised)

import 'dart:async';
import 'dart:convert';

import 'package:angular/angular.dart';
import 'package:http/http.dart';

import 'hero.dart';

@Injectable()
class HeroService {
  static final _headers = {'Content-Type': 'application/json'};
  static const _heroesUrl = 'api/heroes'; // URL to web API
  final Client _http;

  HeroService(this._http);

  Future<List<Hero>> getHeroes() async {
    try {
      final response = await _http.get(_heroesUrl);
      final heroes = _extractData(response)
          .map((value) => new Hero.fromJson(value))
          .toList();
      return heroes;
    } catch (e) {
      throw _handleError(e);
    }
  }

  Future<Hero> create(String name) async {
    try {
      final response = await _http.post(_heroesUrl,
          headers: _headers, body: JSON.encode({'name': name}));
      return new Hero.fromJson(_extractData(response));
    } catch (e) {
      throw _handleError(e);
    }
  }

使用一個 Client 對象

此demo 使用一個Client對象,注入到HeroService構造器中:

HeroService(this._http);

下面的代碼使用client的get()方法取得數據:

lib/src/toh/hero_service.dart (getHeroes)

static const _heroesUrl = 'api/heroes'; // URL to web API
Future<List<Hero>> getHeroes() async {
  try {
    final response = await _http.get(_heroesUrl);
    final heroes = _extractData(response)
        .map((value) => new Hero.fromJson(value))
        .toList();
    return heroes;
  } catch (e) {
    throw _handleError(e);
  }
}

get()方法取得資源URL, 用來鏈接返回英雄數據的服務器.

模擬服務器

若是尚未服務器存在,或者想要在測試期間避免網絡可靠性問題,請不要將BrowserClient做爲Client對象。 相反,您能夠經過使用內存中的Web API來模擬服務器,這是實例源代碼)的做用。

或者,使用JSON文件:

static const _heroesUrl = 'heroes.json'; // URL to JSON file

處理response 對象

getHeroes()方法使用 _extractData() 助手方法映射 _http.get()響應對象到 heroes:

lib/src/toh/hero_service.dart (excerpt)

dynamic _extractData(Response resp) => JSON.decode(resp.body)['data'];

response對象不能在表單中持有數據應用程序能當即使用.使用響應數據, 首先要解碼它.

解碼JSON

響應數據採用JSON字符串形式。 您必須將該字符串反序列化爲對象,您能夠經過調用dart:convert庫中的JSON.decode()方法來執行此操做。 有關解碼和編碼JSON的示例,請參閱Dart庫遊覽的dart:convert部分。

碼後的JSON不會列出英雄。 相反,服務器將JSON結果封裝到具備數據屬性的對象中。 這是傳統的Web API行爲,受安全問題驅動。

不要假設服務器API。 並不是全部的服務器都返回一個帶有數據屬性的對象

不要返回響應對象

儘管getHeroes()有可能返回HTTP響應,但這不是一個好習慣。 數據服務的重點在於隱藏消費者的服務器交互細節。 調用HeroService的組件只須要heroes。 它與負責獲取數據的代碼以及響應對象分離。

始終處理錯誤

處理I / O的一個重要部分是經過準備捕捉它們並與它們作某些事情來預測錯誤。 處理錯誤的一種方法是將錯誤消息傳回組件,以便呈現給用戶,但前提是該消息是用戶能夠理解並採起行動的內容。

這個簡單的應用程序處理getHeroes()錯誤,以下所示:

lib/src/toh/hero_service.dart (excerpt)

Future<List<Hero>> getHeroes() async {
  try {
    final response = await _http.get(_heroesUrl);
    final heroes = _extractData(response)
        .map((value) => new Hero.fromJson(value))
        .toList();
    return heroes;
  } catch (e) {
    throw _handleError(e);
  }
}

Exception _handleError(dynamic e) {
  print(e); // for demo purposes only
  return new Exception('Server error; cause: $e');
}

HeroListComponent 錯誤處理

在HeroListComponent中, _heroService.getHeroes()在一個try子句中, errorMessage 變量有條件的綁定在模板中.errorMessage 變量將被指定一個值:

lib/src/toh/hero_list_component.dart (getHeroes)

Future<Null> getHeroes() async {
  try {
    heroes = await _heroService.getHeroes();
  } catch (e) {
    errorMessage = e.toString();
  }
}

要建立失敗場景,請在HeroService中將API端點重置爲錯誤值。 以後,請記住恢復其原始值。

發送數據到服務器

已經知道了如何使用遠程HTTP服務恢復數據.下一項任務是添加增長英雄並保存到後端的能力.

首先, 服務須要一個組件可以調用來建立和保存一個英雄的方法. 對於此demo, 方法叫作 create() 而且接收新英雄的name:

Future<Hero> create(String name) async {

實現這個方法,你須要知道建立英雄服務的API. 這個簡單的數據服務遵循典型的REST指導方針. 它支持一個POST請求 和GET heroes使用了一樣的端點. 新英雄數據必須在請求體中,結構如同一個Hero 實體可是沒有id 屬性.下面是例子的請求體:

{ "name": "Windstorm" }

服務器生成id並返回新英雄的JSON表示,包括生成的ID。 英雄在一個擁有本身data屬性的響應對象中。

如今你已經知道了服務器的API,下面是create()的實現:

lib/src/toh/hero_service.dart (create)

Future<Hero> create(String name) async {
  try {
    final response = await _http.post(_heroesUrl,
        headers: _headers, body: JSON.encode({'name': name}));
    return new Hero.fromJson(_extractData(response));
  } catch (e) {
    throw _handleError(e);
  }
}

Headers

在_headers對象中, Content-Type指定響應體使用JSON數據格式.

JSON 結果

如同在getHeroes()中, _extractData() 幫助器從response中提取數據.

返回到HeroListComponent中, addHero() 方法 等待服務的異步方法create() 建立一個英雄. 當 create() 執行完成時, addHero() 添加一個新英雄到 heroes 列表:

lib/src/toh/hero_list_component.dart (addHero)

Future<Null> addHero(String name) async {
  name = name.trim();
  if (name.isEmpty) return;
  try {
    heroes.add(await _heroService.create(name));
  } catch (e) {
    errorMessage = e.toString();
  }
}

跨域請求: Wikipedia 例子

儘管在Dart web 應用程序中使用XMLHttpRequests (一般使用助手API, 例如 BrowserClient)進行服務器通訊是一種常見的方法,但此方法並不老是合適.

考慮到安全因素, 瀏覽器阻止XHR訪問遠程服務器(與web頁不在同一個源). 源 是URI 方案, 主機名, 和端口號組成的. 被稱做same-origin方針.

若是服務器支持CORS協議,現代瀏覽器容許來自不一樣來源的服務器的XHR請求。 您能夠在請求標頭中啓用用戶憑據。

一些服務器不支持CORS但支持舊的形式, 只讀的JSONP.

有關JSONP的更多信息,請參閱Stack Overflow

搜索 Wikipedia

下面的例子展現Wikipedia用戶在文本框中打字:

 

Wikipedia 提議了一個CORS API 和一個兼容的 JSONP 搜索 API.

本頁面正在建設中。 如今,請參閱演示源代碼以獲取使用Wikipedia的JSONP API的示例。

相關文章
相關標籤/搜索