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 就足够了。

感谢您的阅读~

相关文章
相关标签/搜索