
- Angular 8 教程
- Angular 8 - 首頁
- Angular 8 - 簡介
- Angular 8 - 安裝
- 建立第一個應用程式
- Angular 8 - 架構
- Angular 元件和模板
- Angular 8 - 資料繫結
- Angular 8 - 指令
- Angular 8 - 管道
- Angular 8 - 響應式程式設計
- 服務和依賴注入
- Angular 8 - HTTP 客戶端程式設計
- Angular 8 - Angular Material
- 路由和導航
- Angular 8 - 動畫
- Angular 8 - 表單
- Angular 8 - 表單驗證
- 身份驗證和授權
- Angular 8 - Web Workers
- Service Workers 和 PWA
- Angular 8 - 伺服器端渲染
- Angular 8 - 國際化 (i18n)
- Angular 8 - 可訪問性
- Angular 8 - CLI 命令
- Angular 8 - 測試
- Angular 8 - Ivy 編譯器
- Angular 8 - 使用 Bazel 構建
- Angular 8 - 向後相容性
- Angular 8 - 工作示例
- Angular 9 - 新特性?
- Angular 8 有用資源
- Angular 8 - 快速指南
- Angular 8 - 有用資源
- Angular 8 - 討論
Angular 8 - HTTP 客戶端程式設計
HTTP 客戶端程式設計是每個現代 Web 應用程式中都必須具備的功能。如今,許多應用程式透過 REST API(基於 HTTP 協議的功能)公開其功能。考慮到這一點,Angular 團隊提供了廣泛的支援來訪問 HTTP 伺服器。Angular 提供了一個單獨的模組,HttpClientModule 和一個服務,HttpClient 來進行 HTTP 程式設計。
讓我們學習如何在本章中使用HttpClient 服務。開發者應該具備 HTTP 程式設計的基礎知識才能理解本章內容。
支出 REST API
進行 HTTP 程式設計的先決條件是對 HTTP 協議和 REST API 技術的基本瞭解。HTTP 程式設計涉及兩部分:伺服器和客戶端。Angular 提供了建立客戶端應用程式的支援。Express,一個流行的 Web 框架,提供了建立伺服器端應用程式的支援。
讓我們使用 Express 框架建立一個支出 REST API,然後使用 Angular HttpClient 服務從我們的ExpenseManager 應用程式訪問它。
開啟命令提示符並建立一個新資料夾,express-rest-api。
cd /go/to/workspace mkdir express-rest-api cd expense-rest-api
使用以下命令初始化一個新的 Node 應用程式:
npm init
npm init 將會詢問一些基本問題,例如專案名稱 (express-rest-api)、入口點 (server.js) 等,如下所示:
This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults. See `npm help json` for definitive documentation on these fields and exactly what they do. Use `npm install <pkg>` afterwards to install a package and save it as a dependency in the package.json file. Press ^C at any time to quit. package name: (expense-rest-api) version: (1.0.0) description: Rest api for Expense Application entry point: (index.js) server.js test command: git repository: keywords: author: license: (ISC) About to write to \path\to\workspace\expense-rest-api\package.json: { "name": "expense-rest-api", "version": "1.0.0", "description": "Rest api for Expense Application", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" } Is this OK? (yes) yes
使用以下命令安裝express、sqlite 和cors 模組:
npm install express sqlite3 cors
建立一個名為sqlitedb.js 的新檔案,並將以下程式碼放入其中:
var sqlite3 = require('sqlite3').verbose() const DBSOURCE = "expensedb.sqlite" let db = new sqlite3.Database(DBSOURCE, (err) => { if (err) { console.error(err.message) throw err }else{ console.log('Connected to the SQLite database.') db.run(`CREATE TABLE expense ( id INTEGER PRIMARY KEY AUTOINCREMENT, item text, amount real, category text, location text, spendOn text, createdOn text )`, (err) => { if (err) { console.log(err); }else{ var insert = 'INSERT INTO expense (item, amount, category, location, spendOn, createdOn) VALUES (?,?,?,?,?,?)' db.run(insert, ['Pizza', 10, 'Food', 'KFC', '2020-05-26 10:10', '2020-05-26 10:10']) db.run(insert, ['Pizza', 9, 'Food', 'Mcdonald', '2020-05-28 11:10', '2020-05-28 11:10']) db.run(insert, ['Pizza', 12, 'Food', 'Mcdonald', '2020-05-29 09:22', '2020-05-29 09:22']) db.run(insert, ['Pizza', 15, 'Food', 'KFC', '2020-06-06 16:18', '2020-06-06 16:18']) db.run(insert, ['Pizza', 14, 'Food', 'Mcdonald', '2020-06-01 18:14', '2020-05-01 18:14']) } } ); } }); module.exports = db
在這裡,我們正在建立一個新的 sqlite 資料庫並載入一些示例資料。
開啟 server.js 並放置以下程式碼:
var express = require("express") var cors = require('cors') var db = require("./sqlitedb.js") var app = express() app.use(cors()); var bodyParser = require("body-parser"); app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.json()); var HTTP_PORT = 8000 app.listen(HTTP_PORT, () => { console.log("Server running on port %PORT%".replace("%PORT%",HTTP_PORT)) }); app.get("/", (req, res, next) => { res.json({"message":"Ok"}) }); app.get("/api/expense", (req, res, next) => { var sql = "select * from expense" var params = [] db.all(sql, params, (err, rows) => { if (err) { res.status(400).json({"error":err.message}); return; } res.json(rows) }); }); app.get("/api/expense/:id", (req, res, next) => { var sql = "select * from expense where id = ?" var params = [req.params.id] db.get(sql, params, (err, row) => { if (err) { res.status(400).json({"error":err.message}); return; } res.json(row) }); }); app.post("/api/expense/", (req, res, next) => { var errors=[] if (!req.body.item){ errors.push("No item specified"); } var data = { item : req.body.item, amount: req.body.amount, category: req.body.category, location : req.body.location, spendOn: req.body.spendOn, createdOn: req.body.createdOn, } var sql = 'INSERT INTO expense (item, amount, category, location, spendOn, createdOn) VALUES (?,?,?,?,?,?)' var params =[data.item, data.amount, data.category, data.location, data.spendOn, data.createdOn] db.run(sql, params, function (err, result) { if (err){ res.status(400).json({"error": err.message}) return; } data.id = this.lastID; res.json(data); }); }) app.put("/api/expense/:id", (req, res, next) => { var data = { item : req.body.item, amount: req.body.amount, category: req.body.category, location : req.body.location, spendOn: req.body.spendOn } db.run( `UPDATE expense SET item = ?, amount = ?, category = ?, location = ?, spendOn = ? WHERE id = ?`, [data.item, data.amount, data.category, data.location,data.spendOn, req.params.id], function (err, result) { if (err){ console.log(err); res.status(400).json({"error": res.message}) return; } res.json(data) }); }) app.delete("/api/expense/:id", (req, res, next) => { db.run( 'DELETE FROM expense WHERE id = ?', req.params.id, function (err, result) { if (err){ res.status(400).json({"error": res.message}) return; } res.json({"message":"deleted", changes: this.changes}) }); }) app.use(function(req, res){ res.status(404); });
在這裡,我們建立了一個基本的 CRUD REST API 來選擇、插入、更新和刪除支出條目。
使用以下命令執行應用程式:
npm run start
開啟瀏覽器,輸入https://:8000/ 並按回車鍵。您將看到以下響應:
{ "message": "Ok" }
這確認我們的應用程式執行良好。
將 url 更改為https://:8000/api/expense,您將看到所有支出條目以 JSON 格式顯示。
[ { "id": 1, "item": "Pizza", "amount": 10, "category": "Food", "location": "KFC", "spendOn": "2020-05-26 10:10", "createdOn": "2020-05-26 10:10" }, { "id": 2, "item": "Pizza", "amount": 14, "category": "Food", "location": "Mcdonald", "spendOn": "2020-06-01 18:14", "createdOn": "2020-05-01 18:14" }, { "id": 3, "item": "Pizza", "amount": 15, "category": "Food", "location": "KFC", "spendOn": "2020-06-06 16:18", "createdOn": "2020-06-06 16:18" }, { "id": 4, "item": "Pizza", "amount": 9, "category": "Food", "location": "Mcdonald", "spendOn": "2020-05-28 11:10", "createdOn": "2020-05-28 11:10" }, { "id": 5, "item": "Pizza", "amount": 12, "category": "Food", "location": "Mcdonald", "spendOn": "2020-05-29 09:22", "createdOn": "2020-05-29 09:22" } ]
最後,我們為支出條目建立了一個簡單的 CRUD REST API,我們可以從 Angular 應用程式訪問該 REST API 來學習 HttpClient 模組。
配置 HTTP 客戶端
讓我們學習如何在本章中配置HttpClient 服務。
HttpClient 服務位於HttpClientModule 模組內,該模組位於 @angular/common/http 包內。
要註冊HttpClientModule 模組:
在AppComponent 中匯入 HttpClientModule
import { HttpClientModule } from '@angular/common/http';
在 AppComponent 的 imports 元資料中包含 HttpClientModule。
@NgModule({ imports: [ BrowserModule, // import HttpClientModule after BrowserModule. HttpClientModule, ] }) export class AppModule {}
建立支出服務
讓我們在我們的ExpenseManager 應用程式中建立一個新的服務ExpenseEntryService 來與支出 REST API 互動。ExpenseEntryService 將獲取最新的支出條目、插入新的支出條目、修改現有的支出條目和刪除不需要的支出條目。
開啟命令提示符並轉到專案根資料夾。
cd /go/to/expense-manager
啟動應用程式。
ng serve
執行以下命令以生成一個 Angular 服務,ExpenseService。
ng generate service ExpenseEntry
這將建立兩個 TypeScript 檔案(支出條目服務及其測試),如下所示:
CREATE src/app/expense-entry.service.spec.ts (364 bytes) CREATE src/app/expense-entry.service.ts (141 bytes)
開啟ExpenseEntryService (src/app/expense-entry.service.ts) 並從 rxjs 庫匯入ExpenseEntry、throwError 和catchError,並從 @angular/common/http 包匯入HttpClient、HttpHeaders 和HttpErrorResponse。
import { Injectable } from '@angular/core'; import { ExpenseEntry } from './expense-entry'; import { throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
將 HttpClient 服務注入到我們的服務中。
constructor(private httpClient : HttpClient) { }
建立一個變數,expenseRestUrl 來指定支出 REST API 端點。
private expenseRestUrl = 'https://:8000/api/expense';
建立一個變數,httpOptions 來設定 HTTP Header 選項。這將由 Angular HttpClient 服務在 HTTP REST API 呼叫期間使用。
private httpOptions = { headers: new HttpHeaders( { 'Content-Type': 'application/json' }) };
完整的程式碼如下:
import { Injectable } from '@angular/core'; import { ExpenseEntry } from './expense-entry'; import { Observable, throwError } from 'rxjs'; import { catchError, retry } from 'rxjs/operators'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class ExpenseEntryService { private expenseRestUrl = 'api/expense'; private httpOptions = { headers: new HttpHeaders( { 'Content-Type': 'application/json' }) }; constructor( private httpClient : HttpClient) { } }
HTTP GET
HttpClient 提供 get() 方法來從網頁獲取資料。主要引數是目標 Web URL。另一個可選引數是具有以下格式的選項物件:
{ headers?: HttpHeaders | {[header: string]: string | string[]}, observe?: 'body' | 'events' | 'response', params?: HttpParams|{[param: string]: string | string[]}, reportProgress?: boolean, responseType?: 'arraybuffer'|'blob'|'json'|'text', withCredentials?: boolean, }
這裡:
headers - 請求的 HTTP 頭,可以是字串、字串陣列或 HttpHeaders 陣列。
observe - 處理響應並返回響應的特定內容。可能的值為 body、response 和 events。observer 的預設選項是 body。
params - 請求的 HTTP 引數,可以是字串、字串陣列或 HttpParams 陣列。
reportProgress - 是否報告程序的進度(true 或 false)。
responseType - 指的是響應的格式。可能的值為arraybuffer、blob、json 和text。
withCredentials - 請求是否包含憑據(true 或 false)。
所有選項都是可選的。
get() 方法將請求的響應作為Observable 返回。當從伺服器接收到響應時,返回的 Observable 會發出資料。
使用get() 方法的示例程式碼如下:
httpClient.get(url, options) .subscribe( (data) => console.log(data) );
型別化響應
get() 方法有一個選項可以返回 observable,它也會發出型別化響應。獲取型別化響應 (ExpenseEntry) 的示例程式碼如下:
httpClient.get<T>(url, options) .subscribe( (data: T) => console.log(data) );
處理錯誤
錯誤處理是 HTTP 程式設計中的一個重要方面。遇到錯誤是 HTTP 程式設計中的常見情況。
HTTP 程式設計中的錯誤可以分為兩類:
客戶端問題可能是由於網路故障、錯誤配置等造成的。如果發生客戶端錯誤,則get() 方法將丟擲ErrorEvent 物件。
伺服器端問題可能是由於錯誤的 URL、伺服器不可用、伺服器程式設計錯誤等造成的。
讓我們為我們的ExpenseEntryService 服務編寫一個簡單的錯誤處理程式。
private httpErrorHandler (error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error("A client side error occurs. The error message is " + error.message); } else { console.error( "An error happened in server. The HTTP status code is " + error.status + " and the error returned is " + error.message); } return throwError("Error occurred. Pleas try again"); }
錯誤函式可以在get() 中呼叫,如下所示:
httpClient.get(url, options) .pipe(catchError(this.httpErrorHandler) .subscribe( (data) => console.log(data) )
處理失敗的請求
正如我們前面提到的,錯誤可能會發生,一種方法是處理它。另一種選擇是嘗試一定次數。如果請求由於網路問題或 HTTP 伺服器暫時離線而失敗,則下一個請求可能會成功。
在這種情況下,我們可以使用rxjs 庫的retry 運算子,如下所示:
httpClient.get(url, options) .pipe( retry(5), catchError(this.httpErrorHandler)) .subscribe( (data) => console.log(data) )
獲取支出條目
讓我們在 ExpenseManager 應用程式中編寫實際程式碼來從支出 REST API 獲取支出。
開啟命令提示符並轉到專案根資料夾。
cd /go/to/expense-manager
啟動應用程式。
ng serve
在ExpenseEntryService (src/app/expense-entry.service.ts) 服務中新增getExpenseEntries() 和httpErrorHandler() 方法。
getExpenseEntries() : Observable<ExpenseEntry[]> { return this.httpClient.get<ExpenseEntry[]>(this.expenseRestUrl, this.httpOptions) .pipe(retry(3),catchError(this.httpErrorHandler)); } getExpenseEntry(id: number) : Observable<ExpenseEntry> { return this.httpClient.get<ExpenseEntry>(this.expenseRestUrl + "/" + id, this.httpOptions) .pipe( retry(3), catchError(this.httpErrorHandler) ); } private httpErrorHandler (error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error("A client side error occurs. The error message is " + error.message); } else { console.error( "An error happened in server. The HTTP status code is " + error.status + " and the error returned is " + error.message); } return throwError("Error occurred. Pleas try again"); }
這裡:
getExpenseEntries() 使用支出端點呼叫get() 方法,並配置錯誤處理程式。此外,它還配置httpClient 在失敗的情況下最多嘗試 3 次。最後,它將伺服器的響應作為型別化的(ExpenseEntry[]) Observable 物件返回。
getExpenseEntry 與 getExpenseEntries() 類似,只是它傳遞 ExpenseEntry 物件的 id 並獲取 ExpenseEntry Observable 物件。
ExpenseEntryService 的完整程式碼如下:
import { Injectable } from '@angular/core'; import { ExpenseEntry } from './expense-entry'; import { Observable, throwError } from 'rxjs'; import { catchError, retry } from 'rxjs/operators'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class ExpenseEntryService { private expenseRestUrl = 'https://:8000/api/expense'; private httpOptions = { headers: new HttpHeaders( { 'Content-Type': 'application/json' }) }; constructor(private httpClient : HttpClient) { } getExpenseEntries() : Observable{ return this.httpClient.get (this.expenseRestUrl, this.httpOptions) .pipe( retry(3), catchError(this.httpErrorHandler) ); } getExpenseEntry(id: number) : Observable { return this.httpClient.get (this.expenseRestUrl + "/" + id, this.httpOptions) .pipe( retry(3), catchError(this.httpErrorHandler) ); } private httpErrorHandler (error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { console.error("A client side error occurs. The error message is " + error.message); } else { console.error( "An error happened in server. The HTTP status code is " + error.status + " and the error returned is " + error.message); } return throwError("Error occurred. Pleas try again"); } }
開啟ExpenseEntryListComponent (src-entry-list-entry-list.component.ts) 並透過建構函式注入ExpenseEntryService,如下所示:
constructor(private debugService: DebugService, private restService : ExpenseEntryService ) { }
更改getExpenseEntries() 函式。呼叫ExpenseEntryService 中的 getExpenseEntries() 方法,而不是返回模擬專案。
getExpenseItems() { this.restService.getExpenseEntries() .subscribe( data =− this.expenseEntries = data ); }
ExpenseEntryListComponent 的完整程式碼如下:
import { Component, OnInit } from '@angular/core'; import { ExpenseEntry } from '../expense-entry'; import { DebugService } from '../debug.service'; import { ExpenseEntryService } from '../expense-entry.service'; @Component({ selector: 'app-expense-entry-list', templateUrl: './expense-entry-list.component.html', styleUrls: ['./expense-entry-list.component.css'], providers: [DebugService] }) export class ExpenseEntryListComponent implements OnInit { title: string; expenseEntries: ExpenseEntry[]; constructor(private debugService: DebugService, private restService : ExpenseEntryService ) { } ngOnInit() { this.debugService.info("Expense Entry List component initialized"); this.title = "Expense Entry List"; this.getExpenseItems(); } getExpenseItems() { this.restService.getExpenseEntries() .subscribe( data => this.expenseEntries = data ); } }
最後,檢查應用程式,您將看到以下響應。

HTTP POST
HTTP POST 與 HTTP GET 類似,不同之處在於 POST 請求將必要的作為已釋出內容的資料與請求一起傳送。HTTP POST 用於將新記錄插入系統。
HttpClient 提供post() 方法,它與get() 類似,但它支援額外的引數來向伺服器傳送資料。
讓我們在我們的ExpenseEntryService 中新增一個新方法,addExpenseEntry(),以新增新的支出條目,如下所示:
addExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> { return this.httpClient.post<ExpenseEntry>(this.expenseRestUrl, expenseEntry, this.httpOptions) .pipe( retry(3), catchError(this.httpErrorHandler) ); }
HTTP PUT
HTTP PUT 與 HTTP POST 請求類似。HTTP PUT 用於更新系統中的現有記錄。
httpClient 提供put() 方法,它與post() 類似。
更新支出條目
讓我們在我們的ExpenseEntryService 中新增一個新方法,updateExpenseEntry(),以更新現有的支出條目,如下所示:
updateExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> { return this.httpClient.put<ExpenseEntry>(this.expenseRestUrl + "/" + expenseEntry.id, expenseEntry, this.httpOptions) .pipe( retry(3), catchError(this.httpErrorHandler) ); }
HTTP DELETE
HTTP DELETE 與 HTTP GET 請求類似。HTTP DELETE 用於刪除系統中的條目。
httpclient 提供delete() 方法,它與get() 類似。
刪除支出條目
讓我們在我們的ExpenseEntryService 中新增一個新方法,deleteExpenseEntry(),以刪除現有的支出條目,如下所示:
deleteExpenseEntry(expenseEntry: ExpenseEntry | number) : Observable<ExpenseEntry> { const id = typeof expenseEntry == 'number' ? expenseEntry : expenseEntry.id const url = `${this.expenseRestUrl}/${id}`; return this.httpClient.delete<ExpenseEntry>(url, this.httpOptions) .pipe( retry(3), catchError(this.httpErrorHandler) ); }