rxjs实现输入框热加载 百度搜索

学了再多的概念,如果没有实战,都是徒劳。这一节将写一个输入框搜索请求后台数据,显示在页面上的例子。

创建一个 rxjs-demo组件,并采用 OnPush策略:

ng g c components/rxjs-demo -c OnPush -s -t

需求分析

  1. 获取输入框 input时的值;

  2. 通过输入值请求接口,返回数据;

    • 每次键入,只请求最新的 value
    • 根据键入速度,节流
    • 如果 inputvalue没发生变化,不重复请求
  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的一个比较完整的流程。相比传统写法,函数式编程会给人一种‘一环扣一环’的感觉,逻辑清晰。虽然可能开始不知道选择什么样的操作符,但是学每一种新技术不都是这样走过来的吗?写的多了,自然就简单了。希望与大家一起进步!

欢迎关注我的公众号,公众号将第一时间更新angular教程:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yanyi24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值