Angular 記錄 - Rxjs 完整處理一個 Http 請求

場景概述


項目中常常有輸入框輸入的時候,向後臺發起請求獲取列表或數據。這個簡單的業務場景在開發的時候須要考慮如下幾點:html

  • 對用戶輸入的內容進行一些校驗
  • 控制請求發送的頻率【防抖】
  • 當輸入框輸入長度爲空時,恢復頁面數據至默認狀態
  • 響應用戶的鍵盤動做【如 enter 進行查詢,esc 進行清空】
  • 確保返回的數據是根據最後輸入的參數進行查詢的

代碼實現


瞭解了業務需求後,咱們結合 rxjs 操做符來控制 input 框實現上述功能typescript

模板視圖 test.component.html 代碼以下:bash

<div class="l-widget-notice-alarmCode">
   <input 
        nz-input 
        placeholder="輸入告警編碼" 
        #noticeInput
    />
</div>
複製代碼

接下來,咱們使用 @ViewChild 屬性裝飾器,從模板視圖中獲取匹配的元素後, 經過 fromEvent 將一個該元素上的事件轉化爲一個Observable:函數

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    
    // Angular 視圖查詢在 ngAfterViewInit 鉤子函數調用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    }
    
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(this.noticeInput.nativeElement, 'keyup');
    }
}
複製代碼

接下來,咱們經過 Pipe 管道操做符來操做事件流:post

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    
    // Angular 視圖查詢在 ngAfterViewInit 鉤子函數調用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    }
    
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(
            this.noticeInput.nativeElement, 
            'keyup'
        );
        
        noticeInputEvent$.pipe(
            debounceTime(300),
            filter((event: KeyboardEvent) => 
                !(event.keyCode >= 37 && event.keyCode <= 40)
            ),
            pluck('target', 'value'),
        ).subscribe(this.loadData);
    }
    
    public loadData(value: string): void {
        // todo => fetch data
        ...
    }
}
複製代碼

上面的代碼中,咱們在 pipe 管道中,使用 debounceTime 操做符,捨棄掉在兩次輸出之間小於指定時間的發出值來完成防抖處理, 經過 filter 操做符過濾符合業務需求的發出值。fetch

最後,咱們經過 pluck 操做符來取得發出對象嵌套屬性,即 event.value 屬性來獲取用戶的輸入值。優化

因爲 Observable 是惰性的,咱們須要主動去觸發這個函數來獲取這個值。 關於 Observable 的介紹能夠 參考 Angular - Observable 概述ui

代碼優化


爲了不訂閱操做可能會致使的內存泄漏,咱們的請求方法還須要作取消訂閱的處理。this

因爲 Observable 也是一種基於發佈、訂閱模式的推送體系,在某個時間點,咱們須要執行取消訂閱操做來釋放系統的內存。不然,應用程序可能會出現內存泄露的狀況。編碼

Observable 訂閱以後會返回一個 subscription 對象,經過調用 subscription 的 unsubscribe 方法來取消當前 Observer 的訂閱,關於取消訂閱,可使用標準的模式來取消訂閱:

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    noticeInputSubscription: Subscription;
    
    // Angular 視圖查詢在 ngAfterViewInit 鉤子函數調用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    }
    
    // 一般咱們在組件銷燬時,去取消訂閱。
    OnDestroy() {
        this.noticeInputSubscription.unsubscribe();
    }
    
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(
            this.noticeInput.nativeElement, 
            'keyup'
        );
        
        this.noticeInputSubscription = noticeInputEvent$.pipe(
            debounceTime(300),
            filter((event: KeyboardEvent) => 
                !(event.keyCode >= 37 && event.keyCode <= 40)
            ),
            pluck('target', 'value'),
        ).subscribe(this.loadData);
    }
    
    public loadData(value: string): void {
        // todo => fetch data
        ...
    }
}
複製代碼

可是這種作法過於麻煩,且一個 Subscription 對應一個 subscribe。

咱們能夠經過 使用 takeUntil 操做符來實現 observable 的自動取消訂閱:

export class NoticeOverviewComponent implements OnInit, OnDestroy, AfterViewInit  {
    @ViewChild('noticeInput', {static: true}) noticeInput: ElementRef;
    // 建立一個在整個組件中使用的訂閱對象 Subject 
    private unsubscribe: Subject<void> = new Subject<void>();
    
    // Angular 視圖查詢在 ngAfterViewInit 鉤子函數調用前完成
    ngAfterViewInit() {
        this.bindNoticeInputEvent();
    }
    
    // 一般咱們在組件銷燬時,去取消訂閱。
    OnDestroy() {
        this.unsubscribe.next();
        this.unsubscribe.complete();
    }
    
    private bindNoticeInputEvent(): void {
        const noticeInputEvent$ = fromEvent(
            this.noticeInput.nativeElement, 
            'keyup'
        );
        noticeInputEvent$.pipe(
            takeUntil(this.unsubscribe),  
            debounceTime(300),
            filter((event: KeyboardEvent) => 
                !(event.keyCode >= 37 && event.keyCode <= 40)
            ),
            pluck('target', 'value'),
        ).subscribe(this.loadData);
    }
    
    public loadData(value: string): void {
        // todo => fetch data
        ...
    }
複製代碼

takeUntil 接受一個 observable ,當接受的 observable 發出值的時候,源 observable 便自動完成了,利用這種機制不只能夠對單個訂閱進行取消,整個組件中均可以利用同一個 unsubscribe: Subject<void> 對象來取消訂閱,由於咱們使用了 Subject,這是一種 多播 的模式

這種機制也是 Angular 中組件銷燬時採用的取消訂閱模式的基礎

舒適提示


大多數時候,咱們能夠在 Pipe 最上層來設置 takeUntil 來處理訂閱,可是在部分 高階流 中,訂閱者所訂閱的 observable 多是由其餘流返回,這個過程也是惰性的,所以若是此時在最上方設置 takeUntil 也極有可能致使內容泄漏的問題。

takeUntil 在一些其餘場景中,也有可能會引起一些問題,能夠經過 配置 rxjs-tslint-rules 中的 rxjs-no-unsafe-takeuntil 規則來確保 takeUntil 的位置放置正確。在這個業務中,咱們在最上方設置 takeUntil 就足夠了。

感謝您的閱讀~

相關文章
相關標籤/搜索