项目中常常有输入框输入的时候,向后台发起请求获取列表或数据。这个简单的业务场景在开发的时候须要考虑如下几点: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 就足够了。
感谢您的阅读~