1. 程式人生 > >Angular6學習筆記13:HTTP(3

Angular6學習筆記13:HTTP(3

HTTP

繼學習筆記12以後,可以模擬向後端傳送get/post/put/delete請求了。在專案中,有一個table,這個table的資料非常多,就好比現在的heroList,需要根據使用者輸入的資訊傳送給遠端伺服器,讓遠端伺服器通過這個資訊,返回搜尋結果。現在要檢索heroList中的資訊,就需要一個輸入框,讓使用者輸入檢索的值,然後將這個值傳送給遠端伺服器(模擬),然後讓遠端伺服器(模擬)返回檢索的結果。

1.在heroService中建立一個關於搜尋的方法:searchHeroes()

 
  1. searchHeroes(term: string): Observable<Hero[]> {

  2. if (!term.trim()) {

  3. return of([]);

  4. }

  5. return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(

  6. tap(_ => this.log(`found heroes matching "${term}"`)),

  7. catchError(this.handleError<Hero[]>('searchHeroes', []))

  8. );

  9. }

在這個方法中,當沒有搜素詞,則返回一個空的陣列,當有搜尋詞的時候,在url中拼接上name

2.在儀表盤的元件中新增搜尋功能

 
  1. <h3>Top Heroes</h3>

  2. <div class="grid grid-pad">

  3. <a *ngFor="let hero of heroes" class="col-1-4"

  4. routerLink="/detail/{{hero.id}}">

  5. <div class="module hero">

  6. <h4>{{hero.name}}</h4>

  7. </div>

  8. </a>

  9. </div>

  10.  
  11. <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元件的模版檔案:

 
  1. <div id="search-component">

  2. <h4>Hero Search</h4>

  3. <input #searchBox id="search-box" (keyup)="search(searchBox.value)" />

  4. <ul class="search-result">

  5. <li *ngFor="let hero of heroes$ | async" >

  6. <a routerLink="/detail/{{hero.id}}">

  7. {{hero.name}}

  8. </a>

  9. </li>

  10. </ul>

  11. </div>

在模版檔案中,建立了keyup 事件,這個keyup事件繫結會呼叫該元件的 search() 方法,並傳入新的搜尋框的值。

*ngFor 是在一個名叫 heroes$ 的列表上迭代,在這裡$ 是一個命名慣例,用來表明 heroes$ 是一個 Observable,而不是陣列。

正常情況下,*ngFor是不能直接使用Observable,此時,就要用到管道,利用管道字元(|),後面緊跟著一個 async,它表示 Angular 的 AsyncPipeAsyncPipe 會自動訂閱到 Observable,這樣你就不用再在元件類中訂閱了。

美化這個HeroSearch元件,修改heroSearch的CSS檔案

 
  1. .search-result li {

  2. border-bottom: 1px solid gray;

  3. border-left: 1px solid gray;

  4. border-right: 1px solid gray;

  5. width:195px;

  6. height: 16px;

  7. padding: 5px;

  8. background-color: white;

  9. cursor: pointer;

  10. list-style-type: none;

  11. }

  12.  
  13. .search-result li:hover {

  14. background-color: #607D8B;

  15. }

  16.  
  17. .search-result li a {

  18. color: #888;

  19. display: block;

  20. text-decoration: none;

  21. }

  22.  
  23. .search-result li a:hover {

  24. color: white;

  25. }

  26. .search-result li a:active {

  27. color: white;

  28. }

  29. #search-box {

  30. width: 200px;

  31. height: 20px;

  32. }

  33.  
  34.  
  35. ul.search-result {

  36. margin-top: 0;

  37. padding-left: 0;

  38. }

修改HeroSearch 的類檔案

 
  1. import { Component, OnInit } from '@angular/core';

  2.  
  3. import { Observable, Subject } from 'rxjs';

  4.  
  5. import {

  6. debounceTime, distinctUntilChanged, switchMap

  7. } from 'rxjs/operators';

  8.  
  9. import { Hero } from '../hero';

  10. import { HeroService } from '../hero.service';

  11.  
  12. @Component({

  13. selector: 'app-hero-search',

  14. templateUrl: './hero-search.component.html',

  15. styleUrls: [ './hero-search.component.css' ]

  16. })

  17. export class HeroSearchComponent implements OnInit {

  18. heroes$: Observable<Hero[]>;

  19. private searchTerms = new Subject<string>();

  20.  
  21. constructor(private heroService: HeroService) {}

  22.  
  23. // Push a search term into the observable stream.

  24. search(term: string): void {

  25. this.searchTerms.next(term);

  26. }

  27.  
  28. ngOnInit(): void {

  29. this.heroes$ = this.searchTerms.pipe(

  30. // wait 300ms after each keystroke before considering the term

  31. debounceTime(300),

  32.  
  33. // ignore new term if same as previous term

  34. distinctUntilChanged(),

  35.  
  36. // switch to new search observable each time the term changes

  37. switchMap((term: string) => this.heroService.searchHeroes(term)),

  38. );

  39. }

  40. }

注意,heroes$ 宣告為一個 Observable

a.RxJS Subject 型別的 searchTerms

Subject 既是可觀察物件的資料來源,本身也是 Observable。可以像訂閱任何 Observable 一樣訂閱 Subject。還可以通過呼叫它的 next(value) 方法往 Observable 中推送一些值,就像 search() 方法中一樣。search() 是通過對文字框的 keystroke 事件的事件繫結來呼叫的。

 
  1. private searchTerms = new Subject<string>();

  2.  
  3. search(term: string): void {

  4. this.searchTerms.next(term);

  5. }

每當使用者在文字框中輸入時,這個事件繫結就會使用文字框的值(搜尋詞)呼叫 search() 函式。 searchTerms 變成了一個能發出搜尋詞的穩定的流。

b.串聯 RxJS 操作符

每當使用者擊鍵後就直接呼叫 searchHeroes() 將導致建立海量的 HTTP 請求,浪費伺服器資源並消耗大量網路流量。

應該怎麼做呢?ngOnInit() 往 searchTerms 這個可觀察物件的處理管道中加入了一系列 RxJS 操作符,用以縮減對 searchHeroes() 的呼叫次數,並最終返回一個可及時給出英雄搜尋結果的可觀察物件(每次都是 Hero[] )。

 
  1. this.heroes$ = this.searchTerms.pipe(

  2. debounceTime(300),

  3. distinctUntilChanged(),

  4. switchMap((term: string) => this.heroService.searchHeroes(term)),

  5. );

  • 在傳出最終字串之前,debounceTime(300) 將會等待,直到新增字串的事件暫停了 300 毫秒。 你實際發起請求的間隔永遠不會小於 300ms。

  • distinctUntilChanged() 會確保只在過濾條件變化時才傳送請求。

  • switchMap() 會為每個從 debounce 和 distinctUntilChanged 中通過的搜尋詞調用搜索服務。 它會取消並丟棄以前的搜尋可觀察物件,只保留最近的。

藉助 switchMap 操作符, 每個有效的擊鍵事件都會觸發一次 HttpClient.get() 方法呼叫。 即使在每個請求之間都有至少 300ms 的間隔,仍然可能會同時存在多個尚未返回的 HTTP 請求。

switchMap() 會記住原始的請求順序,只會返回最近一次 HTTP 方法呼叫的結果。 以前的那些請求都會被取消和捨棄。

注意,取消前一個 searchHeroes() 可觀察物件並不會中止尚未完成的 HTTP 請求。 那些不想要的結果只會在它們抵達應用程式碼之前被捨棄。