3、編寫頁面組件html
4、編寫服務前端
5、引入RxJSgit
6、改造組件github
7、HTTP改造web
本項目源碼放在github數據庫
從這裏開始,咱們要使用RxJS來改造組件和添加新功能了,讓整個項目更加完善。npm
HistoryComponent
組件ng g component hostory
而後在app.component.html
文件夾中添加組件:api
<!-- app.component.html --> <app-history></app-history>
這裏咱們要開始作書本的增刪改查功能,須要先建立一個HistoryService
服務,方便咱們實現這幾個功能:數組
HistoryService
服務ng g service history
而後在生成的ts文件中,增長add
和clear
方法,add
方法用來添加歷史記錄到history
數組中,clear
方法則是清空history
數組:緩存
// history.service.ts export class HistoryService { history: string[] = []; add(history: string){ this.history.push(history); } clear(){ this.history = []; } }
HistoryService
服務在將這個服務,注入到BooksService
中,並改造getBooks
方法:
// books.service.ts import { HistoryService } from './history.service'; constructor( private historyservice: HistoryService ) { } getBooks(): void{ this.historyservice.add('請求書本數據') this.booksservice.getBookList() .subscribe(books => this.books = books); }
也能夠用相同方法,在IndexComponent
中添加訪問首頁書本列表
的記錄。
// index.component.ts import { HistoryService } from '../history.service'; constructor( private booksservice: BooksService, private historyservice: HistoryService ) { } getBooks(): void{ this.historyservice.add('訪問首頁書本列表'); this.booksservice.getBookList() .subscribe(books => this.books = books); }
接下來,將咱們的HistoryService
注入到HistoryComponent
中,而後才能將歷史數據顯示到頁面上:
// 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"
,是爲了防止尚未拿到歷史數據,致使後面的報錯。 (click)="historyservice.clear()"
, 綁定咱們服務中的clear
事件,實現清除緩存。 *ngFor="let item of historyservice.history"
,將咱們的歷史數據渲染到頁面上。
到了這一步,就能看到歷史數據了,每次也換到首頁,都會增長一條。
接下來,咱們要在書本詳情頁也加上歷史記錄的統計,導入文件,注入服務,而後改造getBooks
方法,實現歷史記錄的統計:
// 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(`查看書本${this.books.title},id爲${this.books.id}`); console.log(this.books) } }
這時候就能夠在歷史記錄中,看到這些操做的記錄了,而且清除按鈕也正常使用。
本來我只想寫到上一章,可是想到,咱們實際開發中,哪有什麼本地數據,基本上數據都是要從服務端去請求,因此這邊也有必要引入這一張,模擬實際的HTTP請求。
在這一章,咱們使用Angular提供的 HttpClient
來添加一些數據持久化特性。
而後實現對書本數據進行獲取,增長,修改,刪除和查找功能。
HttpClient
是Angular經過 HTTP 與遠程服務器通信的機制。
這裏咱們爲了讓HttpClient
在整個應用全局使用,因此將HttpClient
導入到根模塊app.module.ts
中,而後把它加入 @NgModule.imports
數組:
import { HttpClientModule } from '@angular/common/http'; @NgModule({ //... imports: [ BrowserModule, AppRoutingModule, HttpClientModule ], //... })
這邊咱們使用 內存 Web API(In-memory Web API) 模擬出的遠程數據服務器通信。
注意: 這個內存 Web API 模塊與 Angular 中的 HTTP 模塊無關。
經過下面命令來安裝:
npm install angular-in-memory-web-api --save
而後在app.module.ts
中導入 HttpClientInMemoryWebApiModule
和 InMemoryDataService
類(後面建立):
// 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 { }
知識點: forRoot()
配置方法接受一個 InMemoryDataService 類(初期的內存數據庫)做爲參數。
而後咱們要建立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條數據 ]; return {books}; } constructor() { } }
這裏先總結InMemoryDbService
所提供的RESTful API,後面都要用到:
例如若是url
是api/books
,那麼
api/books
api/books/id
,好比id
是1
,那麼訪問api/books/1
api/books/id
api/books/id
api/books
如今要爲接下來的網絡請求作一些準備,先在books.service.ts
中引入HTTP符號,而後注入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(`正在執行:${histories}`) } private booksUrl = 'api/books'; // 提供一個API供調用 // ... }
這裏咱們還新增一個私有方法log
和一個私有變量booksUrl
。
接下來咱們要開始發起http請求數據,開始改造getBookList
方法:
// books.service.ts // ... getBookList(): Observable<Books[]> { this.historyservice.add('請求書本數據') return this.http.get<Books[]>(this.booksUrl); } // ...
這裏咱們使用 http.get
替換了 of
,其它沒修改,可是應用仍然在正常工做,這是由於這兩個函數都返回了 Observable<Hero[]>
。
實際開發中,咱們還須要考慮到請求的錯誤處理,要捕獲錯誤,咱們就要使用 RxJS 的 catchError()
操做符來創建對 Observable 結果的處理管道(pipe)。
咱們引入catchError
並改造本來getBookList
方法:
// books.service.ts getBookList(): Observable<Books[]> { this.historyservice.add('請求書本數據') 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}`); // 發出錯誤通知 return of(result as T); // 返回空結果避免程序出錯 }; }
知識點: .pipe()
方法用來擴展 Observable
的結果。 catchError()
操做符會攔截失敗的 Observable。並把錯誤對象傳給錯誤處理器,錯誤處理器會處理這個錯誤。 handleError()
錯誤處理函數作了兩件事,發出錯誤通知和返回空結果避免程序出錯。
這裏還須要使用tap
操做符改造getBookList
方法,來窺探Observable
數據流,它會查看Observable
的值,而後咱們使用log
方法,記錄一條歷史記錄。 tap
回調不會改變這些值自己。
// books.service.ts getBookList(): Observable<Books[]> { return this.http.get<Books[]>(this.booksUrl) .pipe( tap( _ => this.log('請求書本數據')), catchError(this.handleError<Books[]>('getHeroes', [])) ); }
這裏咱們須要在原來DetailComponent
上面,添加一個輸入框、保存按鈕和返回按鈕,就像這樣:
<!-- detail.component.html --> <!-- 前面代碼省略 --> <div> <h2>修改信息:</h2> <label>新標題: <input [(ngModel)]="books.title" placeholder="請輸入新標題"> </label> <button (click)="save()">保存</button> <button (click)="goBack()">返回</button> </div>
這邊切記一點,必定要在app.module.ts
中引入 FormsModule
模塊,並在@NgModule
的imports
中引入,否則要報錯了。
// app.module.ts // ... import { FormsModule } from '@angular/forms'; @NgModule({ // ... imports: [ // ... FormsModule ], // ... })
input
框綁定書本的標題books.title
,而保存按鈕綁定一個save()
方法,這裏還要實現這個方法:
// detail.component.ts save(): void { this.historyservice.updateBooks(this.books) .subscribe(() => this.goBack()); } goBack(): void { this.location.back(); }
這裏經過調用BooksService
的updateBooks
方法,將當前修改後的書本信息修改到源數據中,這裏咱們須要去books.service.ts
中添加updateBooks
方法:
// books.service.ts // ... updateBooks(books: Books): Observable<any>{ return this.http.put(this.booksUrl, books, httpOptions).pipe( tap(_ => this.log(`修改書本的id是${books.id}`)), catchError(this.handleError<Books>(`getBooks請求是id爲${books.id}`)) ) } // ...
知識點: HttpClient.put()
方法接受三個參數:URL 地址
、要修改的數據
和其餘選項
。 httpOptions
常量須要定義在@Injectable
修飾器以前。
如今,咱們點擊首頁,選擇一本書進入詳情,修改標題而後保存,會發現,首頁上這本書的名稱也會跟着改變呢。這算是好了。
咱們能夠新增一個頁面,並添加上路由和按鈕:
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">添加書本</a>
編輯添加書本的頁面:
<!-- add.component.html --> <div class="add"> <h2>添加書本:</h2> <label>標題: <input [(ngModel)]="books.title" placeholder="請輸入標題"> </label> <label>做者: <input [(ngModel)]="books.author" placeholder="請輸入做者"> </label> <label>書本id: <input [(ngModel)]="books.id" placeholder="請輸入書本id"> </label> <label>封面地址: <input [(ngModel)]="books.url" placeholder="請輸入封面地址"> </label> <div><button (click)="add(books)">添加</button></div> </div>
初始化添加書本的數據:
// 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(`新增書本${books.title},id爲${books.id}`); this.location.back(); }); } }
而後在books.service.ts
中添加addBooks
方法,來添加一本書本的數據:
// books.service.ts addBooks(books: Books): Observable<Books>{ return this.http.post<Books>(this.booksUrl, books, httpOptions).pipe( tap((newBook: Books) => this.log(`新增書本的id爲${newBook.id}`)), catchError(this.handleError<Books>('添加新書')) ); }
如今就能夠正常添加書本啦。
這裏咱們先爲每一個書本後面添加一個刪除按鈕,並綁定刪除事件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
方法來刪除:
// 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(`刪除書本${books.title},id爲${books.id}`)), catchError(this.handleError<Books>('刪除書本')) ); }
這裏須要在刪除書本結束後,通知IndexComponent
將數據列表中的這條數據刪除,這裏還須要再瞭解一下Angular 父子組件數據通訊。
而後咱們在父組件IndexComponent
上添加change
事件監聽,並傳入本地的funChange
:
<!-- index.component.html --> <app-books *ngFor="let item of books" [list]="item" (change) = "funChange(item, $event)" ></app-books>
在對應的index.component.ts
中添加funChange
方法:
// index.component.ts funChange(books, $event){ this.books = this.books.filter(h => h.id !== books.id); }
再來,咱們在子組件BooksComponent
上多導入Output
和EventEmitter
,並添加@Output()
修飾器和調用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); }); } }
這樣就實現了咱們父子組件之間的事件傳遞啦,如今咱們的頁面仍是正常運行,而且刪除一條數據後,頁面數據會更新。
仍是在books.service.ts
,咱們添加一個方法getBooks
,來實現經過ID來查找指定書本,由於咱們是經過ID查找,因此返回的是單個數據,這裏就是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(`請求書本的id爲${id}`)), catchError(this.handleError<Books>(`getBooks請求是id爲${id}`)) ) }
注意,這裏 getBooks
會返回 Observable<Books>
,是一個可觀察的單個對象,而不是一個可觀察的對象數組。
這個項目其實很簡單,可是我仍是一步一步的寫下來,一方面讓本身更熟悉Angular,另外一方面也是但願能幫助到更多朋友哈~
最終效果:
本部份內容到這結束
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推薦 | https://github.com/pingan8787... |
JS小冊 | js.pingan8787.com |
微信公衆號 | 前端自習課 |