項目中常常有輸入框輸入的時候,向後臺發起請求獲取列表或數據。這個簡單的業務場景在開發的時候須要考慮如下幾點:html
瞭解了業務需求後,咱們結合 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 就足夠了。
感謝您的閱讀~