更新數據時,我們需要給后臺提供兩項信息:1. 我們要更新哪個教師;2. 更新后的教師的數據應該是什么值。
# RESTful web service
在前后臺的交互過程中,我們前面規定了使用`GET`方法請求`/Teacher`地址來獲取教師的全部數據;規定了使用`GET`方法請求`/Teacher/1`地址來獲取教師ID為`1`的數據;規定了使用`POST`方法請求`/Teacher`地址來新增教師。其實這些都是在遵循`REST`規范。維基百科如是說:
*****
**Representational state transfer**(**REST**) is a [software architectural](https://en.wikipedia.org/wiki/Software_architecture "Software architecture") style that defines a set of constraints to be used for creating [Web services](https://en.wikipedia.org/wiki/Web_service "Web service"). Web services that conform to the REST architectural style, called *RESTful* Web services, provide interoperability between computer systems on the [Internet](https://en.wikipedia.org/wiki/Internet "Internet"). RESTful Web services allow the requesting systems to access and manipulate textual representations of [Web resources](https://en.wikipedia.org/wiki/Web_resource "Web resource")by using a uniform and predefined set of [stateless](https://en.wikipedia.org/wiki/Stateless_protocol "Stateless protocol")operations. Other kinds of Web services, such as [SOAP](https://en.wikipedia.org/wiki/SOAP "SOAP")Web services, expose their own arbitrary sets of operations.[\[1\]](https://en.wikipedia.org/wiki/Representational_state_transfer#cite_note-1)
*****
上面大概是說REST是一種軟件開發的風格(style),而符合這個風格的web服務呢,就是`RESTful Web services`。而我們前面進行前后臺交互時的地址恰恰是按照該風格來制定的。該風格同時規定,進行數據的全部更新時,應該使用`put`方法,并在路徑中傳入要更新的`id`值以及在請求的數據中傳入更新數據。所以我們制定接口規范如下:
```
PUT /Teacher/{id}
```
| Type | Name | Description | Schema |
| ---- | ---- | ---- | ---- |
| Path | id | 更新的教師ID | Long |
| Body | teacher | 更新教師數據 | Teacher |
上述接口描述清晰的說明了請求的方法為`PUT`,請求的地址為`/Teacher/{id}` ,該地址中包括有路徑變量`id` ,該值代表`更新的教師ID` 類型為 `Long`。同時接收請求主體(`Body`),類型為`Teacher`,代表`更新教師數據`。
# 更新數據
我們前面已經掌握了獲取路徑ID值的方法,又知道V層數據變更新將會實時的傳遞給C層,那么代碼就簡單了。
```
constructor(private route: ActivatedRoute, private httpClient: HttpClient, private appComponent: AppComponent ①) {
}
/**
* 提交表單更新數據
*/
onSubmit(): void {
const id = this.route.snapshot.paramMap.get('id');
const url = 'http://localhost:8080/Teacher/' + id;
this.httpClient.put(url, this.teacher ?) ?
.subscribe(() => {
console.log('更新成功');
this.appComponent.ngOnInit();
},
() => {
console.error(`更新數據時發生錯誤,url:${url}`);
});
}
```
* ? PUT方法與POST方法使用相同。第個參數傳入URL信息,第二個參數傳入請求主體。
* ? 由于this.teacher的值會隨著用戶在表單中的輸入而實時變化,所以我們在此直接將this.teacher傳給后臺。
> 如果此時你心里不大清楚this.teacher對象的值,那么可以在使用它之前使用`console.log(this.teacher)`來將其打印到控制臺來查看。
# 代碼重構
代碼重構是軟件開發中非常重要的一環,可以說沒有代碼重構就沒有優秀的項目。本著**不造重復的輪子**的原則,當相同的代碼塊、字符串在項目中出現了多次的時候,就需要思索此處代碼是否需要重構了。當前`TeacherEditComponent`的代碼如下:
```js
import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
import {HttpClient} from '@angular/common/http';
import {AppComponent} from './app.component';
@Component({
templateUrl: './teacher-edit.component.html'
})
export class TeacherEditComponent implements OnInit {
public teacher: any = {};
constructor(private route: ActivatedRoute, private httpClient: HttpClient, private appComponent: AppComponent) {
}
ngOnInit(): void {
const id = this.route.snapshot.paramMap.get('id'); ?
const url = 'http://localhost:8080/Teacher/' + id; ?
this.httpClient.get(url)
.subscribe((data) => {
this.teacher = data;
}, () => {
console.log(`請求 ${url} 時發生錯誤`);
});
}
/**
* 提交表單
*/
onSubmit(): void {
const id = this.route.snapshot.paramMap.get('id'); ?
const url = 'http://localhost:8080/Teacher/' + id; ?
this.httpClient.put(url, this.teacher)
.subscribe(() => {
console.log('更新成功');
this.appComponent.ngOnInit();
},
() => {
console.error(`更新數據時發生錯誤,url:${url}`);
});
}
}
```
* ? 在一個類中,相同的代碼塊出現了兩次。
## 方法一
** 當在一個類中,相同的代碼碼出現了多次的時候,可以把該代碼塊抽離為該類的一個新方法。**
TeacherEditComponent
```
/**
* 獲取與后臺對接的URL
*/
getUrl(): string {
const id = this.route.snapshot.paramMap.get('id');
return 'http://localhost:8080/Teacher/' + id;
}
```
然后在原方法中刪除對應的代碼段并調用新抽離的方法來獲取需要的值。
```
/**
* 獲取與后臺對接的URL
*/
getUrl(): string {
const id = this.route.snapshot.paramMap.get('id');
return 'http://localhost:8080/Teacher/' + id;
}
ngOnInit(): void {
this.httpClient.get(this.getUrl()) ?
.subscribe((data) => {
this.teacher = data;
}, () => {
console.log(`請求 ${this.getUrl()?} 時發生錯誤`);
});
}
/**
* 提交表單
*/
onSubmit(): void {
this.httpClient.put(this.getUrl()?, this.teacher)
.subscribe(() => {
console.log('更新成功');
this.appComponent.ngOnInit();
},
() => {
console.error(`更新數據時發生錯誤,url:${this.getUrl()?}`);
});
}
```
* ? 調用新方法`getUrl()`來直接獲取請求地址的值。
## 方法二
方法一中`getUrl()`被執行了兩次,且每次執行返回的結果必然相同。這種時候當該方法執行的邏輯簡單、運算量不大、沒有資源請求的時候是并沒太大的問題的,但如果其邏輯復雜、運算量大或是有資源請求時,就會帶來不必要的開銷。所以:**當某個方法在多次調用的結果都是確定值時,應該保證該方法的運算只執行一次**,此時我們需要一個變量來緩存方法運算的值。
TeacherEditComponent
```
private url: string; ?
/**
* 獲取與后臺對接的URL
*/
getUrl(): string {
if (this.url === undefined) { ?
const id = this.route.snapshot.paramMap.get('id');
this.url = 'http://localhost:8080/Teacher/' + id; ?
}
return this.url;
}
```
* ? 在類中定義私有變量`url`,設置類型為`string`。
* ? 當`this.url`沒有被初始化時,說明該方法未經過運算。
* ? 將運算的結果緩存到`this.url`中。
此時,當該方法被第1次調用時將計算`url`值;當該方法被第二次調用時,將直接返回第1次調用時計算得到的值,不再重新計算。
# 測試
代碼重構后進行測試,查看是否由于重構給項目帶來的新的BUG。
# 參考文檔
| 名稱 | 鏈接 | 預計學習時長(分) |
| --- | --- | --- |
| 發起PUT請求 | [https://www.angular.cn/guide/http#making-a-put-request](https://www.angular.cn/guide/http#making-a-put-request) | 5 |
| 源碼地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.4.4](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step2.4.4) | - |
- 序言
- 第一章:Hello World
- 第一節:Angular準備工作
- 1 Node.js
- 2 npm
- 3 WebStorm
- 第二節:Hello Angular
- 第三節:Spring Boot準備工作
- 1 JDK
- 2 MAVEN
- 3 IDEA
- 第四節:Hello Spring Boot
- 1 Spring Initializr
- 2 Hello Spring Boot!
- 3 maven國內源配置
- 4 package與import
- 第五節:Hello Spring Boot + Angular
- 1 依賴注入【前】
- 2 HttpClient獲取數據【前】
- 3 數據綁定【前】
- 4 回調函數【選學】
- 第二章 教師管理
- 第一節 數據庫初始化
- 第二節 CRUD之R查數據
- 1 原型初始化【前】
- 2 連接數據庫【后】
- 3 使用JDBC讀取數據【后】
- 4 前后臺對接
- 5 ng-if【前】
- 6 日期管道【前】
- 第三節 CRUD之C增數據
- 1 新建組件并映射路由【前】
- 2 模板驅動表單【前】
- 3 httpClient post請求【前】
- 4 保存數據【后】
- 5 組件間調用【前】
- 第四節 CRUD之U改數據
- 1 路由參數【前】
- 2 請求映射【后】
- 3 前后臺對接【前】
- 4 更新數據【前】
- 5 更新某個教師【后】
- 6 路由器鏈接【前】
- 7 觀察者模式【前】
- 第五節 CRUD之D刪數據
- 1 綁定到用戶輸入事件【前】
- 2 刪除某個教師【后】
- 第六節 代碼重構
- 1 文件夾化【前】
- 2 優化交互體驗【前】
- 3 相對與絕對地址【前】
- 第三章 班級管理
- 第一節 JPA初始化數據表
- 第二節 班級列表
- 1 新建模塊【前】
- 2 初識單元測試【前】
- 3 初始化原型【前】
- 4 面向對象【前】
- 5 測試HTTP請求【前】
- 6 測試INPUT【前】
- 7 測試BUTTON【前】
- 8 @RequestParam【后】
- 9 Repository【后】
- 10 前后臺對接【前】
- 第三節 新增班級
- 1 初始化【前】
- 2 響應式表單【前】
- 3 測試POST請求【前】
- 4 JPA插入數據【后】
- 5 單元測試【后】
- 6 惰性加載【前】
- 7 對接【前】
- 第四節 編輯班級
- 1 FormGroup【前】
- 2 x、[x]、{{x}}與(x)【前】
- 3 模擬路由服務【前】
- 4 測試間諜spy【前】
- 5 使用JPA更新數據【后】
- 6 分層開發【后】
- 7 前后臺對接
- 8 深入imports【前】
- 9 深入exports【前】
- 第五節 選擇教師組件
- 1 初始化【前】
- 2 動態數據綁定【前】
- 3 初識泛型
- 4 @Output()【前】
- 5 @Input()【前】
- 6 再識單元測試【前】
- 7 其它問題
- 第六節 刪除班級
- 1 TDD【前】
- 2 TDD【后】
- 3 前后臺對接
- 第四章 學生管理
- 第一節 引入Bootstrap【前】
- 第二節 NAV導航組件【前】
- 1 初始化
- 2 Bootstrap格式化
- 3 RouterLinkActive
- 第三節 footer組件【前】
- 第四節 歡迎界面【前】
- 第五節 新增學生
- 1 初始化【前】
- 2 選擇班級組件【前】
- 3 復用選擇組件【前】
- 4 完善功能【前】
- 5 MVC【前】
- 6 非NULL校驗【后】
- 7 唯一性校驗【后】
- 8 @PrePersist【后】
- 9 CM層開發【后】
- 10 集成測試
- 第六節 學生列表
- 1 分頁【后】
- 2 HashMap與LinkedHashMap
- 3 初識綜合查詢【后】
- 4 綜合查詢進階【后】
- 5 小試綜合查詢【后】
- 6 初始化【前】
- 7 M層【前】
- 8 單元測試與分頁【前】
- 9 單選與多選【前】
- 10 集成測試
- 第七節 編輯學生
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 功能開發【前】
- 4 JsonPath【后】
- 5 spyOn【后】
- 6 集成測試
- 7 @Input 異步傳值【前】
- 8 值傳遞與引入傳遞
- 9 @PreUpdate【后】
- 10 表單驗證【前】
- 第八節 刪除學生
- 1 CSS選擇器【前】
- 2 confirm【前】
- 3 功能開發與測試【后】
- 4 集成測試
- 5 定制提示框【前】
- 6 引入圖標庫【前】
- 第九節 集成測試
- 第五章 登錄與注銷
- 第一節:普通登錄
- 1 原型【前】
- 2 功能設計【前】
- 3 功能設計【后】
- 4 應用登錄組件【前】
- 5 注銷【前】
- 6 保留登錄狀態【前】
- 第二節:你是誰
- 1 過濾器【后】
- 2 令牌機制【后】
- 3 裝飾器模式【后】
- 4 攔截器【前】
- 5 RxJS操作符【前】
- 6 用戶登錄與注銷【后】
- 7 個人中心【前】
- 8 攔截器【后】
- 9 集成測試
- 10 單例模式
- 第六章 課程管理
- 第一節 新增課程
- 1 初始化【前】
- 2 嵌套組件測試【前】
- 3 async管道【前】
- 4 優雅的測試【前】
- 5 功能開發【前】
- 6 實體監聽器【后】
- 7 @ManyToMany【后】
- 8 集成測試【前】
- 9 異步驗證器【前】
- 10 詳解CORS【前】
- 第二節 課程列表
- 第三節 果斷
- 1 初始化【前】
- 2 分頁組件【前】
- 2 分頁組件【前】
- 3 綜合查詢【前】
- 4 綜合查詢【后】
- 4 綜合查詢【后】
- 第節 班級列表
- 第節 教師列表
- 第節 編輯課程
- TODO返回機制【前】
- 4 彈出框組件【前】
- 5 多路由出口【前】
- 第節 刪除課程
- 第七章 權限管理
- 第一節 AOP
- 總結
- 開發規范
- 備用