Angular6學習筆記13:HTTP(3
HTTP
繼學習筆記12以後,可以模擬向後端傳送get/post/put/delete請求了。在專案中,有一個table,這個table的資料非常多,就好比現在的heroList,需要根據使用者輸入的資訊傳送給遠端伺服器,讓遠端伺服器通過這個資訊,返回搜尋結果。現在要檢索heroList中的資訊,就需要一個輸入框,讓使用者輸入檢索的值,然後將這個值傳送給遠端伺服器(模擬),然後讓遠端伺服器(模擬)返回檢索的結果。
1.在heroService中建立一個關於搜尋的方法:searchHeroes()
-
searchHeroes(term: string): Observable<Hero[]> {
-
if (!term.trim()) {
-
return of([]);
-
}
-
return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
-
tap(_ => this.log(`found heroes matching "${term}"`)),
-
catchError(this.handleError<Hero[]>('searchHeroes', []))
-
);
-
}
在這個方法中,當沒有搜素詞,則返回一個空的陣列,當有搜尋詞的時候,在url中拼接上name
2.在儀表盤的元件中新增搜尋功能
-
<h3>Top Heroes</h3>
-
<div class="grid grid-pad">
-
<a *ngFor="let hero of heroes" class="col-1-4"
-
routerLink="/detail/{{hero.id}}">
-
<div class="module hero">
-
<h4>{{hero.name}}</h4>
-
</div>
-
</a>
-
</div>
-
<app-hero-search></app-hero-search>
這裡會讓這個應用掛了,因為找不到<app-hero-search></app-hero-search>(接下來建立)
3.建立HeroSearchComponent
利用angular CLI 建立元件
ng generate component hero-search
CLI 生成了 HeroSearchComponent
的三個檔案,並把該元件新增到了 AppModule
的宣告中。
修改HeroSearch元件的模版檔案:
-
<div id="search-component">
-
<h4>Hero Search</h4>
-
<input #searchBox id="search-box" (keyup)="search(searchBox.value)" />
-
<ul class="search-result">
-
<li *ngFor="let hero of heroes$ | async" >
-
<a routerLink="/detail/{{hero.id}}">
-
{{hero.name}}
-
</a>
-
</li>
-
</ul>
-
</div>
在模版檔案中,建立了keyup 事件,這個keyup事件繫結會呼叫該元件的 search()
方法,並傳入新的搜尋框的值。
*ngFor
是在一個名叫 heroes$
的列表上迭代,在這裡$
是一個命名慣例,用來表明 heroes$
是一個 Observable
,而不是陣列。
正常情況下,*ngFor是不能直接使用Observable,此時,就要用到管道,利用
管道字元(|
),後面緊跟著一個 async
,它表示 Angular 的 AsyncPipe,AsyncPipe
會自動訂閱到 Observable
,這樣你就不用再在元件類中訂閱了。
美化這個HeroSearch元件,修改heroSearch的CSS檔案
-
.search-result li {
-
border-bottom: 1px solid gray;
-
border-left: 1px solid gray;
-
border-right: 1px solid gray;
-
width:195px;
-
height: 16px;
-
padding: 5px;
-
background-color: white;
-
cursor: pointer;
-
list-style-type: none;
-
}
-
.search-result li:hover {
-
background-color: #607D8B;
-
}
-
.search-result li a {
-
color: #888;
-
display: block;
-
text-decoration: none;
-
}
-
.search-result li a:hover {
-
color: white;
-
}
-
.search-result li a:active {
-
color: white;
-
}
-
#search-box {
-
width: 200px;
-
height: 20px;
-
}
-
ul.search-result {
-
margin-top: 0;
-
padding-left: 0;
-
}
修改HeroSearch 的類檔案
-
import { Component, OnInit } from '@angular/core';
-
import { Observable, Subject } from 'rxjs';
-
import {
-
debounceTime, distinctUntilChanged, switchMap
-
} from 'rxjs/operators';
-
import { Hero } from '../hero';
-
import { HeroService } from '../hero.service';
-
@Component({
-
selector: 'app-hero-search',
-
templateUrl: './hero-search.component.html',
-
styleUrls: [ './hero-search.component.css' ]
-
})
-
export class HeroSearchComponent implements OnInit {
-
heroes$: Observable<Hero[]>;
-
private searchTerms = new Subject<string>();
-
constructor(private heroService: HeroService) {}
-
// Push a search term into the observable stream.
-
search(term: string): void {
-
this.searchTerms.next(term);
-
}
-
ngOnInit(): void {
-
this.heroes$ = this.searchTerms.pipe(
-
// wait 300ms after each keystroke before considering the term
-
debounceTime(300),
-
// ignore new term if same as previous term
-
distinctUntilChanged(),
-
// switch to new search observable each time the term changes
-
switchMap((term: string) => this.heroService.searchHeroes(term)),
-
);
-
}
-
}
注意,heroes$
宣告為一個 Observable
a.RxJS Subject
型別的 searchTerms
Subject
既是可觀察物件的資料來源,本身也是 Observable
。可以像訂閱任何 Observable
一樣訂閱 Subject
。還可以通過呼叫它的 next(value)
方法往 Observable
中推送一些值,就像 search()
方法中一樣。search()
是通過對文字框的 keystroke
事件的事件繫結來呼叫的。
-
private searchTerms = new Subject<string>();
-
search(term: string): void {
-
this.searchTerms.next(term);
-
}
每當使用者在文字框中輸入時,這個事件繫結就會使用文字框的值(搜尋詞)呼叫 search()
函式。 searchTerms
變成了一個能發出搜尋詞的穩定的流。
b.串聯 RxJS 操作符
每當使用者擊鍵後就直接呼叫 searchHeroes()
將導致建立海量的 HTTP 請求,浪費伺服器資源並消耗大量網路流量。
應該怎麼做呢?ngOnInit()
往 searchTerms
這個可觀察物件的處理管道中加入了一系列 RxJS 操作符,用以縮減對 searchHeroes()
的呼叫次數,並最終返回一個可及時給出英雄搜尋結果的可觀察物件(每次都是 Hero[]
)。
-
this.heroes$ = this.searchTerms.pipe(
-
debounceTime(300),
-
distinctUntilChanged(),
-
switchMap((term: string) => this.heroService.searchHeroes(term)),
-
);
-
在傳出最終字串之前,
debounceTime(300)
將會等待,直到新增字串的事件暫停了 300 毫秒。 你實際發起請求的間隔永遠不會小於 300ms。 -
distinctUntilChanged()
會確保只在過濾條件變化時才傳送請求。 -
switchMap()
會為每個從debounce
和distinctUntilChanged
中通過的搜尋詞調用搜索服務。 它會取消並丟棄以前的搜尋可觀察物件,只保留最近的。
藉助 switchMap 操作符, 每個有效的擊鍵事件都會觸發一次 HttpClient.get()
方法呼叫。 即使在每個請求之間都有至少 300ms 的間隔,仍然可能會同時存在多個尚未返回的 HTTP 請求。
switchMap()
會記住原始的請求順序,只會返回最近一次 HTTP 方法呼叫的結果。 以前的那些請求都會被取消和捨棄。
注意,取消前一個 searchHeroes()
可觀察物件並不會中止尚未完成的 HTTP 請求。 那些不想要的結果只會在它們抵達應用程式碼之前被捨棄。