用需求理解rxjs之操作符

需求一:获取鼠标点击或手指触摸的位置数据

分析:无序合并

import { Directive, ElementRef, Output, EventEmitter } from "@angular/core";
import { Observable } from "rxjs/Observable";
import { fromEvent } from "rxjs/observable/fromEvent";
import { merge } from "rxjs/observable/merge";
// 需求一:获取鼠标点击或手指触摸的位置数据

@Directive({
  selector: "[getTouchendOrMouseupPosition]"
})
export class GetTouchendOrMouseupPositionDirective {
  @Output()
  getTouchendOrMouseupPosition: EventEmitter<{
    x: number;
    y: number;
  }> = new EventEmitter();
  constructor(public ele: ElementRef) {}

  ngOnInit() {
    this.getEndPosition().subscribe(res => {
      this.getTouchendOrMouseupPosition.emit(res);
    });
  }
  getEndPosition(): Observable<{ x: number; y: number }> {
    let ele = this.ele.nativeElement;
    let mouseup = fromEvent(ele, "mouseup").map((ev: any) => ({
      x: ev.clientX,
      y: ev.clientY
    }));
    let touchend = fromEvent(ele, "touchend").map((ev: any) => ({
      x: ev.changedTouches[0].clientX,
      y: ev.changedTouches[0].clientY
    }));
    let mouseupAndTouchEnd = merge(mouseup, touchend);
    return mouseupAndTouchEnd;
  }
}
  • 使用
<div style="width: 300px;height: 300px;background-color: red;" (getTouchendOrMouseupPosition)="getTouchendOrMouseupPosition($event)"></div>
getTouchendOrMouseupPosition(e: {x:number,y: number}){
    console.log('最后的位置为:',e);
}

需求二: 逐字打印

分析:递归处理

import {
  Directive,
  Input,
  ElementRef,
  EventEmitter,
  Output
} from "@angular/core";
import { from } from "rxjs/observable/from";
import { interval } from "rxjs/observable/interval";
import "rxjs/add/operator/scan";
import "rxjs/add/operator/zip";

@Directive({
  selector: "[writeWordByString]"
})
export class WriteWordByStringDirective {
  // 书写的字符
  @Input() writeWordByString: string;
  // 书写速度
  @Input() writeSpeed: number = 200;
  @Input() writeLoop: boolean = false;
  // 返回处理结果
  @Output() onWrite: EventEmitter<string> = new EventEmitter();
  constructor(public ele: ElementRef) {}
  ngOnInit() {
    this.getByWord();
  }
  getByWord() {
    let word = from<string>(this.writeWordByString)
      .zip(interval(this.writeSpeed), (x, y) => x)
      .scan((origin, next) => origin + next);
    word.subscribe(
      res => {
        this.onWrite.emit(res);
      },
      () => {},
      () => {
        this.getByWord();
      }
    );
  }
}
  • 使用
<div style="color: #fff;" (onWrite)="str = $event" writeWordByString="大家好,欢迎使用米波后台管理系统!我是开发这套系统的小明。我的扣扣是1037483576。有bug记得反馈我哦!">{{str}}</div>

需求三:关键字搜索

分析:去除无用http请求

import {
  Directive,
  Input,
  ElementRef,
  EventEmitter,
  Output
} from "@angular/core";
import { fromEvent } from "rxjs/observable/fromEvent";
import "rxjs/add/operator/debounceTime";
import "rxjs/add/operator/pluck";
import "rxjs/add/operator/filter";

import { HttpClient } from "@angular/common/http";

@Directive({
  selector: "[searchByKeyword]"
})
export class SearchByKeywordDirective {
  // 请求url
  @Input() searchByKeyword: string;
  @Input() searchKey: string = "key";
  @Output() onSearch: EventEmitter<any> = new EventEmitter();
  constructor(public ele: ElementRef, public http: HttpClient) {}
  ngOnInit() {
    if (this.searchByKeyword.indexOf("?") > -1) {
      // 如果有?不处理
    } else {
      this.searchByKeyword += `?${this.searchKey}=`;
    }
    this.doOnSearch();
  }
  doOnSearch() {
    fromEvent(this.ele.nativeElement, "keyup")
      .debounceTime(300)
      .pluck("target", "value")
      // 去掉无效关键字
      .filter((text) => !!text)
      .switchMap(key => this.http.get(this.searchByKeyword + key))
      .subscribe(res => {
        this.onSearch.emit(res);
      });
  }
}
  • 使用
<input type="text" searchByKeyword="http://api.jirengu.com/fm/getSong.php?key=">

需求创建一个可以随意拖拽的元素

import {
  Directive,
  ElementRef,
  Renderer2,
  Input,
  HostBinding
} from "@angular/core";
import { fromEvent } from "rxjs/observable/fromEvent";
import { merge } from "rxjs/observable/merge";

import "rxjs/add/operator/switchMap";
import "rxjs/add/operator/map";
import "rxjs/add/operator/takeUntil";
import "rxjs/add/operator/throttle";
import "rxjs/add/operator/pairwise";
import "rxjs/add/operator/do";

@Directive({
  selector: "[dragToMove]"
})
export class DragToMoveDirective {
  @HostBinding("style.position") _position: string;
  @HostBinding("style.left.px") _left: number;
  @HostBinding("style.top.px") _top: number;

  @Input("dragToMove")
  set position(val) {
    if (val) {
      this._position = val;
    }
  }

  get position() {
    return this._position || "absolute";
  }
  constructor(public ele: ElementRef, public render: Renderer2) {}

  ngOnInit() {
    this.drag()
      .do((pos: any) => {
        this._position = this.position;
        this._left = pos.left;
        this._top = pos.top;
      })
      .subscribe(pos => {});
  }

  mousedrag() {
    return this.mousedown().switchMap((md: MouseEvent) => {
      const startX = md.offsetX;
      const startY = md.offsetY;
      return this.mousemove()
        .map((mm: MouseEvent) => {
          mm.preventDefault();
          return {
            left: mm.clientX - startX,
            top: mm.clientY - startY
          };
        })
        .takeUntil(this.mouseup());
    });
  }

  touchdrag() {
    return this.touchstart().switchMap((ts: TouchEvent) => {
      const rect = this.getBoundingClientRect();
      const startX = ts.targetTouches[0].clientX - rect.left;
      const startY = ts.targetTouches[0].clientY - rect.top;
      return this.touchmove()
        .map((tm: TouchEvent) => {
          tm.preventDefault();
          return {
            left: tm.targetTouches[0].clientX - startX,
            top: tm.targetTouches[0].clientY - startY
          };
        })
        .takeUntil(this.touchend());
    });
  }

  mousemove() {
    return fromEvent(document, "mousemove");
  }

  touchmove() {
    return fromEvent(document, "touchmove");
  }

  mousedown() {
    return fromEvent(this.ele.nativeElement, "mousedown");
  }

  touchstart() {
    return fromEvent(this.ele.nativeElement, "touchstart");
  }

  mouseup() {
    return fromEvent(document, "mouseup");
  }

  touchend() {
    return fromEvent(document, "touchend");
  }

  drag() {
    return merge(this.mousedrag(), this.touchdrag());
  }

  getBoundingClientRect() {
    return this.ele.nativeElement.getBoundingClientRect();
  }
}
  • 使用
<div dragToMove style="
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  height: 200px;
  width: 200px;
  background: red;
">我是可以拖拽的</div>

需求四 onDestory时停止监听

在angular中,使用Observable时,当组件注销的时候,是要进行一些清理操作的。
普通的做法是把所有的观察着保存在一个数组或者map之类的变量里,onDestory时,遍历unsubscribe
这种方式非常不实用,不雅观。下面我们看一个正确的姿势。

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject, merge, timer, interval } from 'rxjs';
import { takeUntil, filter, map } from 'rxjs/operators';
@Component({
  selector: 'ramda',
  templateUrl: './ramda.component.html',
  styleUrls: ['./ramda.component.scss']
})
export class RamdaComponent implements OnInit, OnDestroy {
  // 某订阅事件1
  sub: Subject<any> = new Subject();
  // 某订阅事件2
  sub2: Subject<any> = new Subject();
  // 某订阅事件3
  sub3: Subject<any> = new Subject();
  // 某订阅事件4
  sub4: Subject<any> = new Subject();
  // 需要取消订阅开关
  needDestory: boolean = false;
  constructor() {}

  ngOnDestroy() {
    this.needDestory = true;
  }

  ngOnInit() {
    this.needDestory = false;
    let takeUtil$ = merge(this.sub, this.sub2, this.sub3, this.sub4).pipe(
      filter(val => this.needDestory)
    );
    let sub = this.sub.pipe(takeUntil(takeUtil$)).subscribe(res => {
      // 得到想要的结果 0,1,2,3,4,5s后模拟注销组件,2s后模拟重新回来,5,6,7,8
      this.log(res);
    });
    let sub2 = this.sub2.pipe(takeUntil(takeUtil$)).subscribe(res => {
    // 得到想要的结果 100,101,102,103,104,5s后模拟注销组件,2s后模拟重新回来,105,106,107,108
      this.log(res);
    });
    // 模拟事件发布
    interval(1000)
      .pipe(takeUntil(takeUtil$))
      .subscribe(res => {
        this.sub.next(res);
        this.sub2.next(100 + res);
      });

    timer(5000).subscribe(res => {
      this.log('5s后模拟注销组件');
      this.ngOnDestroy();
      timer(200).subscribe(res => {
        this.log('2s后模拟重新回来');
        this.needDestory = false;
      });
    });
  }

  log(msg) {
    console.log(msg);
  }
}

小结

过滤

  • take 截取前N个
  • takeUntil 截取流直到流发出
  • takeWhile 截取流直到false
  • skip 跳过前N个
  • skipUntil 跳过直到流发出
  • skipWhild 跳过直到false
  • single 取一个
  • sample 取样
  • last 最后一个
  • first 取第一个或第一个满足条件的值
  • filter 过滤符合条件
  • debounce 动态时间段取一个
  • debounceTime 一定时间段取一个
  • ignoreElements 忽略其他只保留complement和error
  • throttle 节流
  • throttleTime 经过多少时间后取最新值

转换

  • pluck 属性选择
  • buffer 指定流发出是发出
  • bufferCount 达到制定长度后发出
  • bufferTime 达到制定时间后发出
  • bufferToggle
  • bufferWhen
  • groupBy 分组
  • partition 分割
  • scan 时间推移

你有什么需求,留言一起解决!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,504评论 25 707
  • ## 可重入函数 ### 可重入性的理解 若一个程序或子程序可以安全的被并行执行,则称其为可重入的;即当该子程序正...
    夏至亦韵阅读 698评论 0 0
  • 一位故人,走了,并无任何预兆。 会是感应吗?今早特别想听Sarah Brightman,反反复复播着的都是《Tim...
    玉面小白龙女侠阅读 617评论 0 1
  • 没有谁耽误谁这回事。关系是自己选的,被缚于一段关系也是自己选的。走不远多半是因为囚困于自我。耽误这个词太推卸责任了...
    翻译小垃圾阅读 171评论 0 0