本文目錄
- 一江耀、項(xiàng)目起步
- 二养盗、編寫路由組件
- 三、編寫頁(yè)面組件
- 1.編寫單一組件
- 2.模擬數(shù)據(jù)
- 3.編寫主從組件
- 四届腐、編寫服務(wù)
- 1.為什么需要服務(wù)
- 2.編寫服務(wù)
- 五铁坎、引入RxJS
- 六、改造組件
- 1.添加歷史記錄組件
- 2.添加和刪除歷史記錄
- 七犁苏、HTTP改造
- 1.引入HTTP
- 2.通過(guò)HTTP請(qǐng)求數(shù)據(jù)
- 3.通過(guò)HTTP修改數(shù)據(jù)
- 4.通過(guò)HTTP增加數(shù)據(jù)
- 5.通過(guò)HTTP刪除數(shù)據(jù)
- 6.通過(guò)HTTP查找數(shù)據(jù)
本項(xiàng)目源碼放在github
六硬萍、改造組件
從這里開(kāi)始,我們要使用RxJS來(lái)改造組件和添加新功能了围详,讓整個(gè)項(xiàng)目更加完善朴乖。
1.添加歷史記錄組件
- 創(chuàng)建
HistoryComponent
組件
ng g component hostory
然后在app.component.html
文件夾中添加組件:
<!-- app.component.html -->
<app-history></app-history>
2.添加增刪改查功能
這里我們要開(kāi)始做書(shū)本的增刪改查功能,需要先創(chuàng)建一個(gè)HistoryService
服務(wù)助赞,方便我們實(shí)現(xiàn)這幾個(gè)功能:
- 創(chuàng)建
HistoryService
服務(wù)
ng g service history
然后在生成的ts文件中买羞,增加add
和clear
方法,add
方法用來(lái)添加歷史記錄到history
數(shù)組中雹食,clear
方法則是清空history
數(shù)組:
// history.service.ts
export class HistoryService {
history: string[] = [];
add(history: string){
this.history.push(history);
}
clear(){
this.history = [];
}
}
- 使用
HistoryService
服務(wù)
在將這個(gè)服務(wù)畜普,注入到BooksService
中,并改造getBooks
方法:
// books.service.ts
import { HistoryService } from './history.service';
constructor(
private historyservice: HistoryService
) { }
getBooks(): void{
this.historyservice.add('請(qǐng)求書(shū)本數(shù)據(jù)')
this.booksservice.getBookList()
.subscribe(books => this.books = books);
}
也可以用相同方法群叶,在IndexComponent
中添加訪問(wèn)首頁(yè)書(shū)本列表
的記錄吃挑。
// index.component.ts
import { HistoryService } from '../history.service';
constructor(
private booksservice: BooksService,
private historyservice: HistoryService
) { }
getBooks(): void{
this.historyservice.add('訪問(wèn)首頁(yè)書(shū)本列表');
this.booksservice.getBookList()
.subscribe(books => this.books = books);
}
接下來(lái),將我們的HistoryService
注入到HistoryComponent
中街立,然后才能將歷史數(shù)據(jù)顯示到頁(yè)面上:
// history.component.ts
import { HistoryService } from '../history.service';
export class HistoryComponent implements OnInit {
constructor(private historyservice: HistoryService) { }
ngOnInit() {}
}
<!-- history.component.html -->
<div *ngIf="historyservice.history.length">
<h2>操作歷史:</h2>
<div>
<button class="clear"
(click)="historyservice.clear()"
>清除</button>
<div *ngFor="let item of historyservice.history">{{item}}</div>
</div>
</div>
代碼解釋:
*ngIf="historyservice.history.length"
舶衬,是為了防止還沒(méi)有拿到歷史數(shù)據(jù),導(dǎo)致后面的報(bào)錯(cuò)几晤。
(click)="historyservice.clear()"
, 綁定我們服務(wù)中的clear
事件约炎,實(shí)現(xiàn)清除緩存。
*ngFor="let item of historyservice.history"
蟹瘾,將我們的歷史數(shù)據(jù)渲染到頁(yè)面上圾浅。
到了這一步,就能看到歷史數(shù)據(jù)了憾朴,每次也換到首頁(yè)狸捕,都會(huì)增加一條。
接下來(lái)众雷,我們要在書(shū)本詳情頁(yè)也加上歷史記錄的統(tǒng)計(jì)灸拍,導(dǎo)入文件做祝,注入服務(wù),然后改造getBooks
方法鸡岗,實(shí)現(xiàn)歷史記錄的統(tǒng)計(jì):
// detail.component.ts
import { HistoryService } from '../history.service';
export class DetailComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private location: Location,
private booksservice: BooksService,
private historyservice: HistoryService
) { }
//...
getBooks(id: number): void {
this.books = this.booksservice.getBook(id);
this.historyservice.add(`查看書(shū)本${this.books.title}混槐,id為${this.books.id}`);
console.log(this.books)
}
}
這時(shí)候就可以在歷史記錄中,看到這些操作的記錄了轩性,并且清除按鈕也正常使用声登。
七、HTTP改造
原本我只想寫到上一章揣苏,但是想到悯嗓,我們實(shí)際開(kāi)發(fā)中,哪有什么本地?cái)?shù)據(jù)卸察,基本上數(shù)據(jù)都是要從服務(wù)端去請(qǐng)求脯厨,所以這邊也有必要引入這一張,模擬實(shí)際的HTTP請(qǐng)求坑质。
1.引入HTTP
在這一章合武,我們使用Angular提供的 HttpClient
來(lái)添加一些數(shù)據(jù)持久化特性。
然后實(shí)現(xiàn)對(duì)書(shū)本數(shù)據(jù)進(jìn)行獲取洪乍,增加眯杏,修改,刪除和查找功能壳澳。
HttpClient
是Angular通過(guò) HTTP 與遠(yuǎn)程服務(wù)器通訊的機(jī)制岂贩。
這里我們?yōu)榱俗?code>HttpClient在整個(gè)應(yīng)用全局使用,所以將HttpClient
導(dǎo)入到根模塊app.module.ts
中巷波,然后把它加入 @NgModule.imports
數(shù)組:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
//...
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
//...
})
這邊我們使用 內(nèi)存 Web API(In-memory Web API) 模擬出的遠(yuǎn)程數(shù)據(jù)服務(wù)器通訊萎津。
注意: 這個(gè)內(nèi)存 Web API 模塊與 Angular 中的 HTTP 模塊無(wú)關(guān)。
通過(guò)下面命令來(lái)安裝:
npm install angular-in-memory-web-api --save
然后在app.module.ts
中導(dǎo)入 HttpClientInMemoryWebApiModule
和 InMemoryDataService
類(后面創(chuàng)建):
// app.module.ts
import { HttpClientInMemoryWebApiModule } from 'angular-in-memory-web-api';
import { InMemoryDataService } from './in-memory-data.service';
@NgModule({
// ...
imports: [
// ...
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService, {dataEncapsulation:false}
)
],
// ...
})
export class AppModule { }
知識(shí)點(diǎn):
forRoot()
配置方法接受一個(gè) InMemoryDataService 類(初期的內(nèi)存數(shù)據(jù)庫(kù))作為參數(shù)抹镊。
然后我們要?jiǎng)?chuàng)建InMemoryDataService
類:
ng g service InMemoryData
并將生成的in-memory-data.service.ts
修改為:
// in-memory-data.service.ts
import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';
import { Books } from './books';
@Injectable({
providedIn: 'root'
})
export class InMemoryDataService implements InMemoryDbService {
createDb(){
const books = [
{
id: 1,
url: 'https://img3.doubanio.com/view/subject/m/public/s29988481.jpg',
title: '像火焰像灰燼',
author: '程姬',
},
// 省略其他9條數(shù)據(jù)
];
return {books};
}
constructor() { }
}
這里先總結(jié)InMemoryDbService
所提供的RESTful API锉屈,后面都要用到:
例如如果url
是api/books
,那么
- 查詢所有成員:以GET方法訪問(wèn)
api/books
- 查詢某個(gè)成員:以GET方法訪問(wèn)
api/books/id
垮耳,比如id
是1
颈渊,那么訪問(wèn)api/books/1
- 更新某個(gè)成員:以PUT方法訪問(wèn)
api/books/id
- 刪除某個(gè)成員:以DELETE方法訪問(wèn)
api/books/id
- 增加一個(gè)成員:以POST方法訪問(wèn)
api/books
2.通過(guò)HTTP請(qǐng)求數(shù)據(jù)
現(xiàn)在要為接下來(lái)的網(wǎng)絡(luò)請(qǐng)求做一些準(zhǔn)備,先在books.service.ts
中引入HTTP符號(hào)终佛,然后注入HttpClient
并改造:
// books.service.ts
import { HttpClient, HttpHeaders} from '@angular/common/http';
// ...
export class BooksService {
constructor(
private historyservice: HistoryService,
private http: HttpClient
) { }
private log(histories: string){
this.historyservice.add(`正在執(zhí)行:${histories}`)
}
private booksUrl = 'api/books'; // 提供一個(gè)API供調(diào)用
// ...
}
這里我們還新增一個(gè)私有方法log
和一個(gè)私有變量booksUrl
俊嗽。
接下來(lái)我們要開(kāi)始發(fā)起http請(qǐng)求數(shù)據(jù),開(kāi)始改造getBookList
方法:
// books.service.ts
// ...
getBookList(): Observable<Books[]> {
this.historyservice.add('請(qǐng)求書(shū)本數(shù)據(jù)')
return this.http.get<Books[]>(this.booksUrl);
}
// ...
這里我們使用 http.get
替換了 of
铃彰,其它沒(méi)修改绍豁,但是應(yīng)用仍然在正常工作,這是因?yàn)檫@兩個(gè)函數(shù)都返回了 Observable<Hero[]>
牙捉。
實(shí)際開(kāi)發(fā)中竹揍,我們還需要考慮到請(qǐng)求的錯(cuò)誤處理敬飒,要捕獲錯(cuò)誤,我們就要使用 RxJS 的 catchError()
操作符來(lái)建立對(duì) Observable 結(jié)果的處理管道(pipe)芬位。
我們引入catchError
并改造原本getBookList
方法:
// books.service.ts
getBookList(): Observable<Books[]> {
this.historyservice.add('請(qǐng)求書(shū)本數(shù)據(jù)')
return this.http.get<Books[]>(this.booksUrl).pipe(
catchError(this.handleError<Books[]>('getHeroes', []))
);
}
private handleError<T> (operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
this.log(`${operation} 失敗: ${error.message}`); // 發(fā)出錯(cuò)誤通知
return of(result as T); // 返回空結(jié)果避免程序出錯(cuò)
};
}
知識(shí)點(diǎn):
.pipe()
方法用來(lái)擴(kuò)展 Observable
的結(jié)果无拗。
catchError()
操作符會(huì)攔截失敗的 Observable。并把錯(cuò)誤對(duì)象傳給錯(cuò)誤處理器昧碉,錯(cuò)誤處理器會(huì)處理這個(gè)錯(cuò)誤蓝纲。
handleError()
錯(cuò)誤處理函數(shù)做了兩件事,發(fā)出錯(cuò)誤通知和返回空結(jié)果避免程序出錯(cuò)晌纫。
這里還需要使用tap
操作符改造getBookList
方法,來(lái)窺探Observable
數(shù)據(jù)流永丝,它會(huì)查看Observable
的值锹漱,然后我們使用log
方法,記錄一條歷史記錄慕嚷。
tap
回調(diào)不會(huì)改變這些值本身哥牍。
// books.service.ts
getBookList(): Observable<Books[]> {
return this.http.get<Books[]>(this.booksUrl)
.pipe(
tap( _ => this.log('請(qǐng)求書(shū)本數(shù)據(jù)')),
catchError(this.handleError<Books[]>('getHeroes', []))
);
}
3.通過(guò)HTTP修改數(shù)據(jù)
這里我們需要在原來(lái)DetailComponent
上面,添加一個(gè)輸入框喝检、保存按鈕和返回按鈕嗅辣,就像這樣:
<!-- detail.component.html -->
<!-- 前面代碼省略 -->
<div>
<h2>修改信息:</h2>
<label>新標(biāo)題:
<input [(ngModel)]="books.title" placeholder="請(qǐng)輸入新標(biāo)題">
</label>
<button (click)="save()">保存</button>
<button (click)="goBack()">返回</button>
</div>
這邊切記一點(diǎn),一定要在app.module.ts
中引入 FormsModule
模塊挠说,并在@NgModule
的imports
中引入澡谭,不然要報(bào)錯(cuò)了。
// app.module.ts
// ...
import { FormsModule } from '@angular/forms';
@NgModule({
// ...
imports: [
// ...
FormsModule
],
// ...
})
input
框綁定書(shū)本的標(biāo)題books.title
损俭,而保存按鈕綁定一個(gè)save()
方法蛙奖,這里還要實(shí)現(xiàn)這個(gè)方法:
// detail.component.ts
save(): void {
this.historyservice.updateBooks(this.books)
.subscribe(() => this.goBack());
}
goBack(): void {
this.location.back();
}
這里通過(guò)調(diào)用BooksService
的updateBooks
方法,將當(dāng)前修改后的書(shū)本信息修改到源數(shù)據(jù)中杆兵,這里我們需要去books.service.ts
中添加updateBooks
方法:
// books.service.ts
// ...
updateBooks(books: Books): Observable<any>{
return this.http.put(this.booksUrl, books, httpOptions).pipe(
tap(_ => this.log(`修改書(shū)本的id是${books.id}`)),
catchError(this.handleError<Books>(`getBooks請(qǐng)求是id為${books.id}`))
)
}
// ...
知識(shí)點(diǎn):
HttpClient.put()
方法接受三個(gè)參數(shù):URL 地址
雁仲、要修改的數(shù)據(jù)
和其他選項(xiàng)
。
httpOptions
常量需要定義在@Injectable
修飾器之前琐脏。
現(xiàn)在攒砖,我們點(diǎn)擊首頁(yè),選擇一本書(shū)進(jìn)入詳情日裙,修改標(biāo)題然后保存吹艇,會(huì)發(fā)現(xiàn),首頁(yè)上這本書(shū)的名稱也會(huì)跟著改變呢阅签。這算是好了掐暮。
4.通過(guò)HTTP增加數(shù)據(jù)
我們可以新增一個(gè)頁(yè)面,并添加上路由和按鈕:
ng g component add
添加路由:
// app-routing.module.ts
// ...
import { AddComponent } from './add/add.component';
const routes: Routes = [
{ path: '', redirectTo:'/index', pathMatch:'full' },
{ path: 'index', component: IndexComponent},
{ path: 'detail/:id', component: DetailComponent},
{ path: 'add', component: AddComponent},
]
添加路由入口:
<!-- app.component.html -->
<!-- 省略一些代碼 -->
<a routerLink="/add">添加書(shū)本</a>
編輯添加書(shū)本的頁(yè)面:
<!-- add.component.html -->
<div class="add">
<h2>添加書(shū)本:</h2>
<label>標(biāo)題:
<input [(ngModel)]="books.title" placeholder="請(qǐng)輸入標(biāo)題">
</label>
<label>作者:
<input [(ngModel)]="books.author" placeholder="請(qǐng)輸入作者">
</label>
<label>書(shū)本id:
<input [(ngModel)]="books.id" placeholder="請(qǐng)輸入書(shū)本id">
</label>
<label>封面地址:
<input [(ngModel)]="books.url" placeholder="請(qǐng)輸入封面地址">
</label>
<div><button (click)="add(books)">添加</button></div>
</div>
初始化添加書(shū)本的數(shù)據(jù):
// add.component.ts
// ...
import { Books } from '../books';
import { BooksService } from '../books.service';
import { HistoryService } from '../history.service';
import { Location } from '@angular/common';
export class AddComponent implements OnInit {
books: Books = {
id: 0,
url: '',
title: '',
author: ''
}
constructor(
private location: Location,
private booksservice: BooksService,
private historyservice: HistoryService
) { }
ngOnInit() {}
add(books: Books): void{
books.title = books.title.trim();
books.author = books.author.trim();
this.booksservice.addBooks(books)
.subscribe( book => {
this.historyservice.add(`新增書(shū)本${books.title}政钟,id為${books.id}`);
this.location.back();
});
}
}
然后在books.service.ts
中添加addBooks
方法路克,來(lái)添加一本書(shū)本的數(shù)據(jù):
// books.service.ts
addBooks(books: Books): Observable<Books>{
return this.http.post<Books>(this.booksUrl, books, httpOptions).pipe(
tap((newBook: Books) => this.log(`新增書(shū)本的id為${newBook.id}`)),
catchError(this.handleError<Books>('添加新書(shū)'))
);
}
現(xiàn)在就可以正常添加書(shū)本啦樟结。
5.通過(guò)HTTP刪除數(shù)據(jù)
這里我們先為每個(gè)書(shū)本后面添加一個(gè)刪除按鈕,并綁定刪除事件delete
:
<!-- books.component.html -->
<!-- 省略一些代碼 -->
<span class="delete" (click)="delete(list)">X</span>
// books.component.ts
import { BooksService } from '../books.service';
export class BooksComponent implements OnInit {
@Input() list: Books;
constructor(
private booksservice: BooksService
) { }
// ...
delete(books: Books): void {
this.booksservice.deleteBooks(books)
.subscribe();
}
}
然后還要再books.service.ts
中添加deleteBooks
方法來(lái)刪除:
// books.service.ts
deleteBooks(books: Books): Observable<Books>{
const id = books.id;
const url = `${this.booksUrl}/${id}`;
return this.http.delete<Books>(url, httpOptions).pipe(
tap(_ => this.log(`刪除書(shū)本${books.title}精算,id為${books.id}`)),
catchError(this.handleError<Books>('刪除書(shū)本'))
);
}
這里需要在刪除書(shū)本結(jié)束后瓢宦,通知IndexComponent
將數(shù)據(jù)列表中的這條數(shù)據(jù)刪除,這里還需要再了解一下Angular 父子組件數(shù)據(jù)通信灰羽。
然后我們?cè)诟附M件IndexComponent
上添加change
事件監(jiān)聽(tīng)驮履,并傳入本地的funChange
:
<!-- index.component.html -->
<app-books *ngFor="let item of books" [list]="item"
(change) = "funChange(item, $event)"
></app-books>
在對(duì)應(yīng)的index.component.ts
中添加funChange
方法:
// index.component.ts
funChange(books, $event){
this.books = this.books.filter(h => h.id !== books.id);
}
再來(lái),我們?cè)谧咏M件BooksComponent
上多導(dǎo)入Output
和EventEmitter
廉嚼,并添加@Output()
修飾器和調(diào)用emit
:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
export class BooksComponent implements OnInit {
// ...
@Output()
change = new EventEmitter()
// ...
delete(books: Books): void {
this.booksservice.deleteBooks(books)
.subscribe(()=>{
this.change.emit(books);
});
}
}
這樣就實(shí)現(xiàn)了我們父子組件之間的事件傳遞啦玫镐,現(xiàn)在我們的頁(yè)面還是正常運(yùn)行,并且刪除一條數(shù)據(jù)后怠噪,頁(yè)面數(shù)據(jù)會(huì)更新恐似。
6.通過(guò)HTTP查找數(shù)據(jù)
還是在books.service.ts
,我們添加一個(gè)方法getBooks
傍念,來(lái)實(shí)現(xiàn)通過(guò)ID來(lái)查找指定書(shū)本矫夷,因?yàn)槲覀兪峭ㄟ^(guò)ID查找,所以返回的是單個(gè)數(shù)據(jù)憋槐,這里就是Observable<Books>
類型:
// books.service.ts
getBooks(id: number): Observable<Books>{
const url = `${this.booksUrl}/${id}`;
return this.http.get<Books>(url).pipe(
tap( _ => this.log(`請(qǐng)求書(shū)本的id為${id}`)),
catchError(this.handleError<Books>(`getBooks請(qǐng)求是id為${id}`))
)
}
注意双藕,這里 getBooks
會(huì)返回 Observable<Books>
,是一個(gè)可觀察的單個(gè)對(duì)象阳仔,而不是一個(gè)可觀察的對(duì)象數(shù)組忧陪。
八、結(jié)語(yǔ)
這個(gè)項(xiàng)目其實(shí)很簡(jiǎn)單近范,但是我還是一步一步的寫下來(lái)赤嚼,一方面讓自己更熟悉Angular,另一方面也是希望能幫助到更多朋友哈~
最終效果:
本部分內(nèi)容到這結(jié)束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787/Leo_Reading/issues |
JS小冊(cè) | js.pingan8787.com |
微信公眾號(hào) | 前端自習(xí)課 |