【CuteJavaScript】Angular6入門項目(4.改造組件和添加HTTP服務)

本文目錄

本項目源碼放在githubhtml

6、改造組件

從這裏開始,咱們要使用RxJS來改造組件和添加新功能了,讓整個項目更加完善。前端

1.添加歷史記錄組件

  • 建立HistoryComponent組件
ng g component hostory
複製代碼

而後在app.component.html文件夾中添加組件:git

<!-- app.component.html -->
<app-history></app-history>
複製代碼

2.添加增刪改查功能

這裏咱們要開始作書本的增刪改查功能,須要先建立一個HistoryService服務,方便咱們實現這幾個功能:github

  • 建立HistoryService服務
ng g service history
複製代碼

而後在生成的ts文件中,增長addclear方法,add方法用來添加歷史記錄到history數組中,clear方法則是清空history數組:web

// 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中添加訪問首頁書本列表的記錄。npm

// 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中,而後才能將歷史數據顯示到頁面上:api

// 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",將咱們的歷史數據渲染到頁面上。數組

到了這一步,就能看到歷史數據了,每次也換到首頁,都會增長一條。緩存

圖片5-1

接下來,咱們要在書本詳情頁也加上歷史記錄的統計,導入文件,注入服務,而後改造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)
    }
}
複製代碼

圖片5-2

這時候就能夠在歷史記錄中,看到這些操做的記錄了,而且清除按鈕也正常使用。

7、HTTP改造

本來我只想寫到上一章,可是想到,咱們實際開發中,哪有什麼本地數據,基本上數據都是要從服務端去請求,因此這邊也有必要引入這一張,模擬實際的HTTP請求。

1.引入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中導入 HttpClientInMemoryWebApiModuleInMemoryDataService 類(後面建立):

// 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,後面都要用到:
例如若是urlapi/books,那麼

  • 查詢全部成員:以GET方法訪問api/books
  • 查詢某個成員:以GET方法訪問api/books/id,好比id1,那麼訪問api/books/1
  • 更新某個成員:以PUT方法訪問api/books/id
  • 刪除某個成員:以DELETE方法訪問api/books/id
  • 增長一個成員:以POST方法訪問api/books

2.經過HTTP請求數據

如今要爲接下來的網絡請求作一些準備,先在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', []))
        );
}
複製代碼

3.經過HTTP修改數據

這裏咱們須要在原來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模塊,並在@NgModuleimports中引入,否則要報錯了。

// 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();
}
複製代碼

這裏經過調用BooksServiceupdateBooks方法,將當前修改後的書本信息修改到源數據中,這裏咱們須要去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修飾器以前。

如今,咱們點擊首頁,選擇一本書進入詳情,修改標題而後保存,會發現,首頁上這本書的名稱也會跟着改變呢。這算是好了。

4.經過HTTP增長數據

咱們能夠新增一個頁面,並添加上路由和按鈕:

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>('添加新書'))
    );
}
複製代碼

如今就能夠正常添加書本啦。

圖片5-3

5.經過HTTP刪除數據

這裏咱們先爲每一個書本後面添加一個刪除按鈕,並綁定刪除事件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上多導入OutputEventEmitter,並添加@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);
        });
    }
}
複製代碼

這樣就實現了咱們父子組件之間的事件傳遞啦,如今咱們的頁面仍是正常運行,而且刪除一條數據後,頁面數據會更新。

6.經過HTTP查找數據

仍是在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>,是一個可觀察的單個對象,而不是一個可觀察的對象數組。

8、結語

這個項目其實很簡單,可是我仍是一步一步的寫下來,一方面讓本身更熟悉Angular,另外一方面也是但願能幫助到更多朋友哈~ 最終效果:

圖片結果

本部份內容到這結束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787/Leo_Reading/issues
JS小冊 js.pingan8787.com
微信公衆號 前端自習課

前端自習課
相關文章
相關標籤/搜索