
- Angular 教程
- Angular - 首頁
- Angular - 概述
- Angular - 特性
- Angular - 優點與缺點
- Angular 基礎
- Angular - 環境搭建
- Angular - 第一個應用
- Angular - MVC 架構
- Angular 元件
- Angular - 元件
- Angular - 元件生命週期
- Angular - 檢視封裝
- Angular - 元件互動
- Angular - 元件樣式
- Angular - 巢狀元件
- Angular - 內容投影
- Angular - 動態元件
- Angular - 元素
- Angular 模板
- Angular - 模板
- Angular - 文字插值
- Angular - 模板語句
- Angular - 模板中的變數
- Angular - SVG 作為模板
- Angular 資料繫結
- Angular - 資料繫結及其型別
- Angular - 資料繫結
- Angular - 事件繫結
- Angular - 屬性繫結
- Angular - 屬性繫結
- Angular - 類和樣式繫結
- Angular 指令
- Angular - 指令
- Angular - 內建指令
- Angular 管道
- Angular - 管道
- Angular - 使用管道轉換資料
- Angular 依賴注入
- Angular - 依賴注入
- Angular HTTP 客戶端程式設計
- Angular - 服務
- Angular - HTTP 客戶端
- Angular - 請求
- Angular - 響應
- Angular - GET 請求
- Angular - PUT 請求
- Angular - DELETE 請求
- Angular - JSON-P
- Angular - 使用 HTTP 進行 CRUD 操作
- Angular 路由
- Angular - 路由
- Angular - 導航
- Angular - Angular Material
- Angular 動畫
- Angular - 動畫
- Angular 表單
- Angular - 表單
- Angular - 表單驗證
- Angular Service Workers & PWA
- Angular - Service Workers & PWA
- Angular 測試
- Angular - 測試概述
- Angular NgModule
- Angular - 模組介紹
- Angular 高階
- Angular - 身份驗證和授權
- Angular - 國際化
- Angular - 可訪問性
- Angular - Web Workers
- Angular - 伺服器端渲染
- Angular - Ivy 編譯器
- Angular - 使用 Bazel 構建
- Angular - 向後相容性
- Angular - 響應式程式設計
- Angular - 指令和元件之間共享資料
- Angular 工具
- Angular - CLI
- Angular 其他
- Angular - 第三方控制元件
- Angular - 配置
- Angular - 資料顯示
- Angular - 裝飾器和元資料
- Angular - 基本示例
- Angular - 錯誤處理
- Angular - 測試和構建專案
- Angular - 生命週期鉤子
- Angular - 使用者輸入
- Angular - 最新動態?
- Angular 有用資源
- Angular - 快速指南
- Angular - 有用資源
- Angular - 討論
Angular - 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
使用以下命令初始化一個新的節點應用程式:
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); });
在這裡,我們建立了一個基本的 CURD 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" } ]
最後,我們為支出條目建立了一個簡單的 CURD 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 頭選項。這將在 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 請求將必要的數
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) ); }