Angular8教程

Angular8 客户端编程

Http 客户端编程是每个现代人必备的功能Web应用程序。如今,许多应用程序通过 REST API(基于 HTTP 协议的功能)公开其功能。考虑到这一点,Angular Team 为访问 HTTP 服务器提供了广泛的支持。 Angular 提供了一个单独的模块 HttpClientModule 和一个服务 HttpClient 来进行 HTTP 编程。
让我们在本章中学习如何使用 HttpClient服务。开发者需要具备 Http 编程的基础知识才能理解本章内容。

Expense REST API

进行Http编程的前提是具备Http协议和REST API技术的基础知识。 Http 编程涉及服务器和客户端两部分。 Angular 支持创建客户端应用程序。 Express 一种流行的 Web 框架,为创建服务器端应用程序提供支持。
让我们使用 express 框架创建一个 Expense 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、sqlitecors 模块-
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 来选择、插入、更新和删除Expense条目。
使用以下命令运行应用程序-
npm run start
打开浏览器,输入 http://localhost:8000/ 并回车。您将看到以下回复-
{ 
   "message": "Ok" 
}
它确认我们的应用程序运行良好。
将 url 更改为 http://localhost:8000/api/expense,您将看到 JSON 格式的所有Expense条目。
[
   {
      "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"
   }
]
最后,我们为Expense条目创建了一个简单的 CURD REST API,我们可以从我们的 Angular 应用程序访问 REST API 来学习 HttpClient 模块。

配置Http客户端

让我们在本章中学习如何配置 HttpClient服务。
HttpClient 服务在 HttpClientModule 模块内可用,该模块在@angular/common/http 包内可用。
注册 HttpClientModule 模块-
AppComponent
中导入HttpClientModule
import { HttpClientModule } from '@angular/common/http';
在 AppComponent 的导入元数据中包含 HttpClientModule。
@NgModule({ 
   imports: [ 
      BrowserModule, 
      // import HttpClientModule after BrowserModule. 
      HttpClientModule, 
   ] 
}) 
export class AppModule {}

创建Expense服务

让我们在 ExpenseManager 应用程序中创建一个新服务 ExpenseEntryService 以与 Expense REST API 交互。 ExpenseEntryService 将获取最新的Expense条目、插入新的Expense条目、修改现有的Expense条目并删除不需要的Expense条目。
打开命令提示符并转到项目根文件夹。
cd /go/to/expense-manager
启动应用程序。
ng serve
运行以下命令以生成 Angular 服务 ExpenseService
ng generate service ExpenseEntry
这将创建两个 Typescript 文件(Expense条目服务及其测试),如下所示-
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、throwErrorcatchError 和从@angular/common/http 包中导入 HttpClient、HttpHeadersHttpErrorResponse
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 以指定 Expense Rest API 端点。
private expenseRestUrl = 'http://localhost: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 获取

HttpClient 提供了 get() 方法来从网页中获取数据。主要参数是目标网址。另一个可选参数是具有以下格式的选项对象-
{
   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。 params-请求的 HTTP 参数,可以是字符串、字符串数组或 HttpParams 数组。 reportProgress-是否报告进程的进度(真或假)。 responseType-指响应的格式。可能的值为 arraybuffer、blob、jsontext withCredentials-请求是否有凭据(真或假)。
所有选项都是可选的。
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) )

获取Expense条目

让我们进行实际编码,以从 ExpenseManager 应用程序中的 Expense Rest API 获取Expense。
打开命令提示符并转到项目根文件夹。
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() 使用Expense端点调用 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 = 'http://localhost: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() 以添加新的Expense条目,如下所述-
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()

更新Expense条目

让我们在 ExpenseEntryService 中添加一个新方法 updateExpenseEntry() 来更新现有的Expense条目,如下所述:
updateExpenseEntry(expenseEntry: ExpenseEntry): Observable<ExpenseEntry> {
   return this.httpClient.put<ExpenseEntry>(this.expenseRestUrl + "/" + expenseEntry.id, expenseEntry, this.httpOptions)
   .pipe(
      retry(3),
      catchError(this.httpErrorHandler)
   );
}

HTTP 删除

HTTP DELETE 类似于 http GET 请求。 HTTP DELETE 用于删除系统中的条目。
httpclient 提供了 delete() 方法,类似于 get()

删除Expense条目

让我们在 ExpenseEntryService 中添加一个新方法 deleteExpenseEntry() 来删除现有的expe如下所述的 nse 条目-
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)
   );
}
昵称: 邮箱:
Copyright © 2022 立地货 All Rights Reserved.
备案号:京ICP备14037608号-4