I. Overview
RxJs
It is a library specially designed to handle asynchronous operations JavaScript
. RxJs
Provides a core class: Observable
, several key classes: Observer
, Schedulers
, Subjects
. Think RxJs
of it as event-oriented Lodash
.
RxJs
The library is based on pure functions, so it has powerful features and stable functions.
RxJs
The library has a complete set of operators for controlling the event flow in observable objects, which can monitor and control the event flow at various stages.
RxJs
There are several key concepts in it:
- Observable(
Observable
). It can be compared toPromise
an object, within which asynchronous operations can be performed, and after the asynchronous operation is completed, the results are transferred to the outside of the observable object. - The callback function in the observer (
Observer
) analogthen
method is used to receive data passed by the observable object. - Subscription(
Subscribe
) analogythen
method, when the observable object emits data, the subscriber can receive the data.
2. Observable
- Creation method:
new Observable()
Created through the constructor, receiving a method as a parameter. This method has a subscriber parameter, through whichnext()
data can be sent out.
const observable = new Observable((subscriber) => {
subscriber.next(1);
})
Observable
The code in the object is lazy and is different fromPromise
the object. It is executed immediately when it is created;Observable
the code in the object will only be executed when it is subscribed.Observable
Subscriptions to objects requireobservable.subscribe()
method calls, similar tofunction.call()
function calls.observable.subscribe()
The method accepts an object as a parameter, which contains a callback function after the observable object emits data. WhenObservable
the object executes the method, the functionsubscriber.next()
will be triggered , and the function can receive the parameters of the method.observer.next
observer.next
subscriber.next()
const observer = {
next: (value: any) => {
console.log(value);
}
}
observable.subscribe(observer);
Observable
The object'ssubscriber.next()
method can be called multiple times, and each call will beobserver.next
monitored.Observable
An object can be subscribed multiple times, and the code in it will be executed each time it is subscribed.
For example, in the following code,Observable
the object is subscribed to two different observers.
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
})
const observer1 = {
next: (value: any) => {
console.log("我是订阅者一号,接收到:"+value);
}
}
const observer2 = {
next: (value: any) => {
console.log("我是订阅者二号,接收到:"+value);
}
}
observable.subscribe(observer1);
observable.subscribe(observer2);
Console output:
observable.subscribe()
The method can be compared tofunction.call()
, which will execute the function call and obtain the return value of the function execution. Butcall()
the method can only be usedreturn
once and can only receive the return value of one synchronous event, whilesubscribe()
the method can be usednext
multiple times and can receive the return value of all synchronous and asynchronous events.
const observable = new Observable((subscriber) => {
subscriber.next(1);
setTimeout(function () {
subscriber.next(2);
})
})
const observer1 = {
next: (value: any) => {
console.log(value);
}
}
observable.subscribe(observer1);
Observable
The code in the object is executed synchronously
const observable = new Observable((subscriber) => {
subscriber.next(1);
setTimeout(function () {
subscriber.next(2);
})
})
const observer1 = {
next: (value: any) => {
console.log(value);
}
}
console.log('just before subscribe');
observable.subscribe(observer1);
console.log('just after subscribe');
Observable
subscriber
In addition tonext
the methods used to send data, there are two methods in the object :complete()
the method is used to tell the observer that the data is sent;error(errorMessage)
the method is used to tell the observer that an internal error has occurred. Correspondingly, in addition to attributes,observer
observers also have attributes and properties, which point to events and event callback functions respectively. Methods cannot pass parameters. In addition, whether it is a method or a method, it means that the observation is over. If it is used to send data later, it cannot be observed by the observer.next
complete
error
complete
error
complete
subscriber.complete()
subscriber.error(errorMessage)
subscriber.next()
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.complete();
subscriber.next(2); // 不能输出了
})
const observer1 = {
next: (value: any) => {
console.log(value);
},
complete: () => {
console.log('complete');
}
}
observable.subscribe(observer1);
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.error('发生了一个致命的错误');
subscriber.next(2);
})
const observer1 = {
next: (value: any) => {
console.log(value);
},
error: (error: any) => {
console.log(error);
}
}
observable.subscribe(observer1);
三、Subject
Observable
The code in the observable object created through the constructor will be executed once every time an observer is subscribed. The observable object created by using Subject
it is an empty observable object. When subscribing, it will not be executed immediately. You need to manually call this observable object to send out data. Scenarios related to broadcasting can use this kind of observable object. You can subscribe to this observable object in multiple places and call the method after getting the data next
. At this time, all subscribers will receive the data at the same time. The observable object created using Subject
it is multicast, allowing values to be passed to multiple observers; the Observable
observable object created using it is unicast, and each subscriber has Observable
independent execution.
// 创建一个空的可观察对象
const subject = new Subject();
// 有多个观察者订阅这个可观察对象
subject.subscribe({
next: (v) => console.log(`observerA: ${
v}`)
});
subject.subscribe({
next: (v) => console.log(`observerB: ${
v}`)
});
// 异步执行next方法获取数据,数据会被发送给所有的观察者
setTimeout(() => subject.next(1), 1000);
setTimeout(() => subject.next(2), 2000);
四、BehaviorSubject
BehaviorSubject
It is Subject
a variant, it saves the latest data sent, that is, the "current value". When an observer subscribes to it, the latest data sent is obtained. BehaviorSubject
Suitable for expressing "value over time". BehaviorSubject
A default value can be received. The first observer will execute immediately when subscribing to it and receive this default value.
const behaviorSubject = new BehaviorSubject('默认值');
// 订阅的时候会立即执行一次,输出:ObserverA默认值
behaviorSubject.subscribe(value => {
console.log('ObserverA'+value)
})
// 执行并被ObserverA观察到,输出:ObserverA第一次改变值
behaviorSubject.next('第一次改变值')
// 执行,被ObserverA观察到,输出:ObserverA第二次改变值
behaviorSubject.next('第二次改变值')
// 又一个订阅,此时behaviorSubject保存的是最新发送出去的值:第二次改变值
// 虽然这个订阅在next执行之后,但是仍然可以获取behaviorSubject里面保存的最新的数据
// 输出:ObserverB第二次改变值
behaviorSubject.subscribe(value => {
console.log('ObserverB'+value)
})
五、ReplaySubject
ReplaySubject
Save all sent data. ReplaySubject
Record Observable
multiple values during execution and play them back to a new observer. When creating ReplaySubject
, you can specify how many values to replay, giving the most recent values to the new observer.
// 规定回放的次数为2.即当有新的订阅者的时候,
// 会查看历史发送数据,并把最近的2条数据发送给新的订阅者
const replaySubject = new ReplaySubject(2);
// 这个订阅者会接收到replaySubject的所有数据
replaySubject.subscribe(value => {
console.log(`replaySubjectA: ${
value}`)
})
replaySubject.next(1);
replaySubject.next(2);
replaySubject.next(3);
// 这个订阅者会接收之前的两条数据以及之后的所有数据
replaySubject.subscribe(value => {
console.log(`replaySubjectB: ${
value}`)
})
6. Operator
Operators are Observable
methods on types, such as .map(...)
, .filter(...)
, .merge(...)
, etc. An operator is essentially a pure function (pure function)
. Operators are RxJs
the most useful part of the library. Operators are divided into instance operators and static operators. Instance operators are Observable
methods of instances, and static operators are Observable
static methods of classes. The most common static operator is the creation operator, which is used to create Observable对象
.
1.of
of
Used to create simple Observable
, emit the given parameters
const observableOf = of(1, '第二个元素', {
name: '第三个元素'});
observableOf.subscribe(
value => console.log(value)
)
2.from
Creates one from an array, array-like object, Promise
, iterator object, or object-like object .Observable
Observable
const arr = [1, 2, 3];
// from创建一个Observable对象,依次将数组中的元素发射出去
const observable1 = from(arr)
observable1.subscribe((value) => {
console.log(`数字数组元素:${
value}`)
})
const promiseArr = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
// 依次将数组中的promise对象发射出去
const observable2 = from(promiseArr)
observable2.subscribe((value) => {
// value是每一个promise对象元素
value.then((value) => {
console.log(`Promise.then接收到的数据:${
value}`)
})
})
from
Methods can Promise
convert objects to Observable
objects. The data sent by promise
the object will be received by the observer's callback functionresolve
next
// from方法可以将Promise对象转换为Observable对象
const promise = new Promise(resolve => {
setTimeout(() => {
resolve('promise resolved')
}, 1000)
})
const obs1 = from(promise)
obs1.subscribe(value => console.log(value))
3.fromEvent
public static fromEvent(target: EventTargetLike, eventName: string, options: EventListenerOptions, selector: SelectorMethodSignature): Observable
Parameter one: DOM
element or NodeList
Parameter two: event name click
, mousedown
etc.
Parameter three: optional value, parameters passed to the event listening function
Parameter four: optional value, function processing result, receiving parameters of the event processing function, should be returned single value
// 第三个参数是配置对象,once:true表示只执行一次
// 第四个参数是点击事件执行完毕的回调函数
const observable = fromEvent(document, 'click',{
once:true},(event)=>{
console.log(event)
console.log('执行了');
});
observable.subscribe((event) => {
// 如果fromEvent中传递了第四个参数,即点击事件执行完毕的回调函数,
// 那么这里的回调函数不会接收到任何参数
// 如果fromEvent中没有传递第四个参数,这里会接受到一个event对象
console.log(event)
console.log('document被点击');
})
4.map
The map
function is similar to the method in the array map
.
// 返回一个操作方法,接受一个observable对象作为参数
// 对observable对象发出的每一个值进行处理,返回一个新的observable对象
const operatorFunction = map((v: number) => v * v);
const result = operatorFunction(of(1, 2, 3));
result.subscribe(x => console.log(x));
Simulation map
method
// 模拟map方法
// 1.接受一个函数作为参数
// 2.返回值也是一个函数
// 3.返回的这个函数,需要接受一个observable对象作为参数,
// 4.返回值是一个新的observable对象,并且发出的数据是对observable参数发出的数据进行fn处理
function myMap(fn: (arg0: any) => void) {
return function (observable: any) {
return Observable.create((observer: any) => {
observable.subscribe((value: any) => {
observer.next(fn(value));
})
})
}
}
5.forkJoin
forkJoin
is similar to Promise.all
a method. Assuming that there are multiple Observable
objects sending data, forkJoin
you can wait for all Observable
objects to send data and return an Observable
object. Asynchronous methods through forkJoin
composition are executed in parallel.
// 模拟axios请求
const getUsername =function (){
return new Promise(resolve => {
setTimeout(() => {
resolve('张三')
}, 1000)
})
}
const getAge =function (){
return new Promise(resolve => {
setTimeout(() => {
resolve(78)
}, 2000)
})
}
// forkJoin()接受一个对象,对象指向的是Observable对象发出的数据
// forkJoin()返回一个Observable对象。
// subscribe里面直接传递回调函数就表示对发出的数据执行这个函数
forkJoin({
username: from(getUsername()),
age: from(getAge())
}).subscribe(console.log)
6.pluck
pluck
If Observable
the data emitted by the object is an object, you can use it pluck
to get the corresponding properties. Accepts a string as a parameter, traverses the emitted data, and obtains the corresponding attributes.
const observable = new Observable(subscriber => {
subscriber.next({
name: 'Brian'})
subscriber.next({
name: 'Joe'})
subscriber.next({
name: 'Sue'})
})
const pluckFn = pluck('name')
pluckFn(observable).subscribe(console.log)
7.interval
Send an incremental amount of data every period of time. Accepts a time interval as argument.
interval(1000).subscribe((value) => {
console.log(value);
})
Send data every 1000ms
8.pipe()
In RxJS
the library, pipe
it is a functional programming method that is used to string multiple operators together to transform the Observable
data stream. Because RxJS
there are many data transformations and operators present in , and nesting these operations directly would make the code difficult to read and maintain, you can use pipe
methods to combine operators into a pipeline. Similar to lodash
the function combination in, the following code is equivalent to of(1,2,3)
executing map
the processing first, then executing filter
the processing, and finally returning a new processed one Observable
.
of(1,2,3)
.pipe(
map(x => x * 2),
filter(x => x > 3)
).subscribe(console.log)
9.switchMap
switchMap
emits the result of applying the projection function to each item emitted Observable
by source , and only receives the latest projected internal value.Observable
Observable
let observable = new Observable(subscriber => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 1000);
})
const newObservable = observable.pipe(switchMap(value => {
return of(value + '被switchMap了');
}))
newObservable.subscribe(console.log);
10.take
Receives a number as argument and returns the first few in the data stream.
ngOnInit(): void {
of(1,2,3)
.pipe(
take(2)
).subscribe(console.log)
}
11.takeWhile
Accepts a conditional function as a parameter and returns a data stream that satisfies the condition. Search from front to back and stop directly if the conditions are not met.
of(1,100,3)
.pipe(
takeWhile(value => value < 10)
).subscribe(console.log)
12. takeUntil
Accepts an observable object as a parameter. When the observable object emits data, the main data source will stop sending data.
// 创建一个异步发出数据的Observable
const observable= new Observable(subscriber => {
setTimeout(() => {
subscriber.next(1)
},5000)
})
// 使用Interval创建一个Observable
// 当oberable发出数据时,使用takeUntil停止
interval(1000)
.pipe(takeUntil(observable))
.subscribe(console.log)
The console will stop printing after printing 4 numbers.
13.throttleTime
Throttle. When observable events send data to the outside at high frequency, in order to avoid frequently triggering subscription callbacks, use throttleTime
operators to pass a time interval as a parameter to ensure that data is only sent once within the specified time interval. The following code, if clicked frequently on the page, will only trigger the subscriber's callback function once within 1 second.
fromEvent(document, 'click')
.pipe(throttleTime(1000))
.subscribe(() => console.log('Clicked!'));
14.debounceTime
Anti-shake. Accepts a time interval as a parameter, and only responds to the last data sent in this time interval. If the event is triggered again within this interval, the subscriber's callback function will not be triggered. The subscriber's callback function will be triggered only if the event is triggered only once within a certain time interval. The following code is frequently clicked on the page. The subscriber's callback function will only be triggered when the page is clicked only once within 1 second.
fromEvent(document, 'click')
.pipe(debounceTime(1000))
.subscribe(() => console.log('Clicked!'));
Throttling is suitable for scenarios where users click, because when clicking, they expect immediate results. In order to prevent repeated clicks, throttling is added. Anti-shake is suitable for user input scenarios, delaying the execution of the callback function because the input event may not have ended yet. Therefore, throttling is suitable for scenarios where one operation takes effect, and anti-shake is suitable for scenarios where results are obtained through multiple operations.
15.distinctUntilChanged
distinctUntilChanged
does not need to receive parameters. Check whether the data sent this time is the same as the data sent last time. If they are the same, they are skipped, if they are different, they are passed to the subscriber.
of(1, 1, 2, 2,3)
.pipe(distinctUntilChanged())
.subscribe(console.log);
7. Case 1: Mouse dragging elements
In component template
<style>
#box{
width: 100px;
height: 100px;
background-color: pink;
position: absolute;
left: 0;
top: 0;
}
</style>
<!--使用#标识需要在组件类中获取的html元素-->
<div id="box" #box></div>
In component class
// 实现元素拖拽
// 1.鼠标按下的时候,计算鼠标相对于box的位置,即鼠标相对于视口的位置-box相对于视口的偏移量
// 2.为document增加mousemove事件,并计算鼠标相对于视口的位置:clientX和clientY
// 3.鼠标移动的时候,计算鼠标相对于视口的位置:clientX和clientY
// 4.设置box的偏移量:鼠标相对于视口的位置-鼠标相对于box的位置
// 5.鼠标抬起的时候,移除mousemove事件
// 使用ViewChild获取dom元素
@ViewChild('box') box: ElementRef | undefined;
ngAfterViewInit() {
// 移动事件
const moveObservable = fromEvent(document, 'mousemove')
.pipe(takeUntil(fromEvent(document, 'mouseup')))
// this.box.nativeElement为dom元素。?为可选链,防止报错
// fromEvent为dom元素绑定事件并将事件转为Observable
fromEvent(this.box?.nativeElement, 'mousedown').pipe(
// 处理数据的操作都放在操作符中
map(event => ({
// 获取鼠标相对于box的位置
distanceX: (event as MouseEvent).clientX - this.box?.nativeElement.offsetLeft,
distanceY: (event as MouseEvent).clientY - this.box?.nativeElement.offsetTop
})),
// 流式操作,这里获取的是map处理后的结果
// 使用对象结构赋值直接获取distanceX和distanceY
switchMap(({
distanceX, distanceY}) =>
fromEvent(document, 'mousemove').pipe(
// 当鼠标抬起时停止移动事件数据的发送
takeUntil(fromEvent(document, 'mouseup')),
map(moveEvent => ({
left: (moveEvent as MouseEvent).clientX - distanceX,
top: (moveEvent as MouseEvent).clientY - distanceY
})
)
)))
// 使用解构赋值直接获取left和top
.subscribe(({
left, top}) => {
this.box!.nativeElement.style.left = left + 'px';
this.box!.nativeElement.style.top = top + 'px';
})
}
8. Case 2: Search
In component template
<input type="text" placeholder="请输入搜索内容" #search>
In component class
@ViewChild('search') search: ElementRef | undefined;
ngAfterViewInit() {
fromEvent(this.search?.nativeElement, 'input')
.pipe(
debounceTime(1000), // 防抖。适用于输入框,防止用户输入过快
map((event: any) => event.target.value), // 获取输入的内容
distinctUntilChanged(), // 过滤掉重复的值。如果用户删除之后快速输入,和上次的值一样,就阻止触发请求
switchMap((value: string) =>
this.response(value) // switchMap应该返回Observable,但是这里返回Promise也正常运行了,应该是能自动转成Obervable
)
)
.subscribe((res: any) => {
console.log(res)
}
)
}
// 模拟一个请求
private response(data: any) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: data,
status: 200
})
}, 1000)
})
}
9. Case 3: Serial request
Clicking a button requires sending two requests in sequence. The parameters of the second request need to depend on the return value of the first request.
Component template
<button #btn>点击获取数据</button>
Component class
@ViewChild('btn') btn: ElementRef | undefined;
ngAfterViewInit() {
fromEvent(this.btn?.nativeElement, 'click')
.pipe(
concatMap(event=>this.response('Hello')),
pluck('data'),
concatMap((data: any) => this.response(data + ' Angular'))
)
.subscribe((res: any) => {
console.log(res)
}
)
}
// 模拟一个请求
private response(data: any) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: data,
status: 200
})
}, 1000)
})
}
After clicking, you get the result of the second request.