需求一:获取鼠标点击或手指触摸的位置数据
分析:无序合并
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;
}
}
- 使用
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 = new EventEmitter();
constructor(public ele: ElementRef) {}
ngOnInit() {
this.getByWord();
}
getByWord() {
let word = from(this.writeWordByString)
.zip(interval(this.writeSpeed), (x, y) => x)
.scan((origin, next) => origin + next);
word.subscribe(
res => {
this.onWrite.emit(res);
},
() => {},
() => {
this.getByWord();
}
);
}
}
- 使用
{{str}}
需求三:关键字搜索
分析:去除无用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 = 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);
});
}
}
- 使用
需求创建一个可以随意拖拽的元素
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();
}
}
- 使用
我是可以拖拽的
需求四 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 = new Subject();
// 某订阅事件2
sub2: Subject = new Subject();
// 某订阅事件3
sub3: Subject = new Subject();
// 某订阅事件4
sub4: Subject = 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 时间推移