ng g c components/rxjs-demo -c OnPush -s -t
需求分析
1. 获取输入框 input 时的值;
2. 通过输入值请求接口,返回数据;
- 每次键入,只请求最新的
value
- 根据键入速度,节流
- 如果
input
的value
没发生变化,不重复请求
3. 错误处理。
页面代码
一个输入框,一个遍历的列表展示结果。
<div class="container"> <div class="autocomplete mt-2"> <input #input class="form-control" placeholder="search..." /> <ul class="list-group mt-2"> <li class="list-group-item" *ngFor="let item of resList">{{item?.q}}</li> </ul> </div> </div>
封装百度请求服务
因为百度接口是 JSONP
请求, rxjs
提供的 ajax
操作符不支持,所以我们需要使用一个请求插件并安装:jsonp-good
import {from, Observable} from 'rxjs'; import jsonpG from 'jsonp-good'; interface BaiduRes { q: string; } @Injectable() class BaiduService { readonly url = 'https://www.baidu.com/sugrec'; list(wd: string): Observable<BaiduRes[]> { // 因为这个插件返回的是Promise,所以用from转换 return from(jsonpG({ url: this.url, funcName: 'jQuery110203052522071732855_1604236886158', params: { prod: 'pc', from: 'pc_web', wd } }).then((res: {g: BaiduRes[]}) => res.g)); } }
获取input输入值
获取 DOM
:
@ViewChild('input', {static: true}) private inputEl: ElementRef;
在 ngAfterViewInit
中获取输入框值:
ngAfterViewInit(): void { fromEvent(this.inputEl.nativeElement, 'input').pipe( debounceTime(500), // 键入0.5秒后输出值,并忽略0.5秒间隔内的输入 pluck('target', 'value') ).subscribe(res => { console.log(res); }); }
RxJs
一起处理请求呢?fromEvent(this.inputEl.nativeElement, 'input').pipe( debounceTime(500), // 键入0.5秒后输出值,并忽略0.5秒间隔内的输入 pluck('target', 'value'), switchMap((value: string) => this.baiduServer.list(value)) // 每一次请求之前会取消上一次的请求 ).subscribe(res => { this.resList = res; console.log('resList', this.resList); // und });
resList
能够获取到,但是页面并没有更新。OnPush
策略下,虽然 input
事件会触发变更检测,但是后面的请求是异步操作。当输入时,请求并没有返回结果,也就不会有变化。所以,我们需要手动触发变更检测。... constructor(private baiduServer: BaiduService, private cdr: ChangeDetectorRef) { } ... fromEvent(this.inputEl.nativeElement, 'input').pipe( debounceTime(500), // 键入0.5秒后输出值,并忽略0.5秒间隔内的输入 pluck('target', 'value'), switchMap((value: string) => this.baiduServer.list(value))// 每一次请求之前会取消上一次的请求 ).subscribe(res => { this.resList = res; this.cdr.markForCheck(); // 手动变更检测 });
fromEvent(this.inputEl.nativeElement, 'input').pipe( debounceTime(500), // 键入0.5秒后输出值,并忽略0.5秒间隔内的输入 pluck('target', 'value'), distinctUntilChanged(), // 只有本次value与上一次value不一致时才发射数据 switchMap((value: string) => value.length ? this.baiduServer.list(value) : of([]))// 每一次请求之前会取消上一次的请求 ).subscribe(res => { this.resList = res; this.cdr.markForCheck(); // 手动变更检测 });
至此,我们想要的功能全部实现。完整代码:
// rxjs-demo.component.ts import { Component, OnInit, ChangeDetectionStrategy, Injectable, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core'; import {from, fromEvent, Observable, of, throwError} from 'rxjs'; import jsonpG from 'jsonp-good'; import {catchError, debounceTime, distinctUntilChanged, pluck, switchMap} from 'rxjs/operators'; interface BaiduRes { q: string; } @Injectable() class BaiduService { readonly url = 'https://www.baidu.com/sugrec'; list(wd: string): Observable<BaiduRes[]> { // 因为这个插件返回的是Promise,所以用from转换 return from(jsonpG({ url: this.url, funcName: 'jQuery110203052522071732855_1604236886158', params: { prod: 'pc', from: 'pc_web', wd } }).then((res: {g: BaiduRes[]}) => res.g)); } } @Component({ selector: 'app-rxjs-demo', template: \` <div class="container"> <div class="autocomplete mt-2"> <input #input class="form-control" placeholder="search..." /> <ul class="list-group mt-2"> <li class="list-group-item" *ngFor="let item of resList">{{item?.q}}</li> </ul> </div> </div> \`, styles: [], changeDetection: ChangeDetectionStrategy.OnPush, providers: [BaiduService] }) export class RxjsDemoComponent implements OnInit, AfterViewInit { @ViewChild('input', {static: true}) private inputEl: ElementRef; resList: BaiduRes[]; constructor(private baiduServer: BaiduService, private cdr: ChangeDetectorRef) { } ngOnInit(): void {} ngAfterViewInit(): void { fromEvent(this.inputEl.nativeElement, 'input').pipe( debounceTime(500), // 键入0.5秒后输出值,并忽略0.5秒间隔内的输入 pluck('target', 'value'), distinctUntilChanged(), // 只有本次value与上一次value不一致时才发射数据 // 每一次请求之前会取消上一次的请求 switchMap((value: string) => value.length ? this.baiduServer.list(value) : of([])), catchError(err => throwError(err)) ).subscribe( res => { this.resList = res; this.cdr.markForCheck(); // 手动变更检测 }, error => console.error(error) ); } }
这虽然是一个简单的Demo,但是依旧能看出使用 RxJs
的一个比较完整的流程。相比传统写法,函数式编程会给人一种‘一环扣一环’的感觉,逻辑清晰。虽然可能开始不知道选择什么样的操作符,但是学每一种新技术不都是这样走过来的吗?写的多了,自然就简单了。希望与大家一起进步!
前面逍遥乐也分享过一篇文章《史上最全Rxjs从入门到精通的学习知识点整理》,看完这个,你就能熟悉各种rxjs的基本操作了。
本文转载自:岩弈,版权归原作者所有,本博客仅以学习目的的传播渠道,不作版权和内容观点阐述,转载时根据场景需要有所改动。
最新评论