Angular Study Notes 7: RxJs

I. Overview

RxJsIt is a library specially designed to handle asynchronous operations JavaScript. RxJsProvides a core class: Observable, several key classes: Observer, Schedulers, Subjects. Think RxJsof it as event-oriented Lodash.
RxJsThe library is based on pure functions, so it has powerful features and stable functions.
RxJsThe 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.
RxJsThere are several key concepts in it:

  1. Observable( Observable). It can be compared to Promisean 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.
  2. The callback function in the observer ( Observer) analog thenmethod is used to receive data passed by the observable object.
  3. Subscription( Subscribe) analogy thenmethod, when the observable object emits data, the subscriber can receive the data.

2. Observable

  1. Creation method: new Observable()Created through the constructor, receiving a method as a parameter. This method has a subscriber parameter, through which next()data can be sent out.
const observable = new Observable((subscriber) => {
    
    
    subscriber.next(1);
})
  1. ObservableThe code in the object is lazy and is different from Promisethe object. It is executed immediately when it is created; Observablethe code in the object will only be executed when it is subscribed.
  2. ObservableSubscriptions to objects require observable.subscribe()method calls, similar to function.call()function calls. observable.subscribe()The method accepts an object as a parameter, which contains a callback function after the observable object emits data. When Observablethe object executes the method, the function subscriber.next()will be triggered , and the function can receive the parameters of the method.observer.nextobserver.nextsubscriber.next()
const observer = {
    
    
    next: (value: any) => {
    
    
        console.log(value);
    }
}
observable.subscribe(observer);
  1. ObservableThe object's subscriber.next()method can be called multiple times, and each call will be observer.nextmonitored.
  2. ObservableAn 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, Observablethe 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:
Insert image description here

  1. observable.subscribe()The method can be compared to function.call(), which will execute the function call and obtain the return value of the function execution. But call()the method can only be used returnonce and can only receive the return value of one synchronous event, while subscribe()the method can be used nextmultiple 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);

Insert image description here

  1. ObservableThe 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');

Insert image description here

  1. ObservablesubscriberIn addition to nextthe 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, observerobservers 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.nextcompleteerrorcompleteerrorcompletesubscriber.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);

Insert image description here

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);

Insert image description here

三、Subject

ObservableThe 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 Subjectit 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 Subjectit is multicast, allowing values ​​to be passed to multiple observers; the Observableobservable object created using it is unicast, and each subscriber has Observableindependent 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);

Insert image description here

四、BehaviorSubject

BehaviorSubjectIt is Subjecta variant, it saves the latest data sent, that is, the "current value". When an observer subscribes to it, the latest data sent is obtained. BehaviorSubjectSuitable for expressing "value over time". BehaviorSubjectA 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)
})

Insert image description here

五、ReplaySubject

ReplaySubjectSave all sent data. ReplaySubjectRecord Observablemultiple 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 Observablemethods on types, such as .map(...), .filter(...), .merge(...), etc. An operator is essentially a pure function (pure function). Operators are RxJsthe most useful part of the library. Operators are divided into instance operators and static operators. Instance operators are Observablemethods of instances, and static operators are Observablestatic methods of classes. The most common static operator is the creation operator, which is used to create Observable对象.

1.of

ofUsed to create simple Observable, emit the given parameters

const observableOf = of(1, '第二个元素', {
    
    name: '第三个元素'});
observableOf.subscribe(
    value => console.log(value)
)

Insert image description here

2.from

Creates one from an array, array-like object, Promise, iterator object, or object-like object .ObservableObservable

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}`)
    })
})

Insert image description here
fromMethods can Promiseconvert objects to Observableobjects. The data sent by promisethe object will be received by the observer's callback functionresolvenext

// 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

fromEvent

public static fromEvent(target: EventTargetLike, eventName: string, options: EventListenerOptions, selector: SelectorMethodSignature): Observable

Parameter one: DOMelement or NodeList
Parameter two: event name click, mousedownetc.
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 mapmethod

// 模拟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.alla method. Assuming that there are multiple Observableobjects sending data, forkJoinyou can wait for all Observableobjects to send data and return an Observableobject. Asynchronous methods through forkJoincomposition 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 Observablethe data emitted by the object is an object, you can use it pluckto 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)

Insert image description here

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

Insert image description here

8.pipe()

In RxJSthe library, pipeit is a functional programming method that is used to string multiple operators together to transform the Observable data stream. Because RxJSthere 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 lodashthe function combination in, the following code is equivalent to of(1,2,3)executing mapthe processing first, then executing filterthe 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 Observableby source , and only receives the latest projected internal value.ObservableObservable

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);

Insert image description here

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)
}

Insert image description here

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)

Insert image description here

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.

Insert image description here

13.throttleTime

Throttle. When observable events send data to the outside at high frequency, in order to avoid frequently triggering subscription callbacks, use throttleTimeoperators 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);

Insert image description here

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.

Insert image description here

Guess you like

Origin blog.csdn.net/weixin_45855469/article/details/130991565