? ? ? ?看到Observable和RxJS就感覺很親切绢片,因為之前做Android開發(fā)的時候接觸過RxJava嵌屎。Observable和RxJS的相關(guān)知識最好的文檔還是官方文檔 https://www.angular.cn/guide/observables 強(qiáng)烈推薦大家看官方文檔毫胜。
一、可觀察對象(Observable)
? ? ? ?可觀察對象(Observable)在Angular 中使用非常廣泛。可觀察對象支持在應(yīng)用中的發(fā)布者和訂閱者之間傳遞消息撮躁。 在需要進(jìn)行事件處理、異步編程和處理多個值的時候买雾,可觀察對象相對其它技術(shù)有著顯著的優(yōu)點把曼。
? ? ? ?可觀察對象的使用本質(zhì)可以認(rèn)為是一個觀察者模式杨帽。簡單的流程就是一個觀察者(Observer)通過subscribe()方法訂閱一個可觀察對象(Observable)。訂閱之后觀察者(Obsever)對可觀察者(Observable)發(fā)射的數(shù)據(jù)或數(shù)據(jù)序列就能作出響應(yīng)(next函數(shù)發(fā)射數(shù)據(jù))嗤军。涉及到三個東西:觀察者(Observer)注盈、可觀察者(Observable)、訂閱(subscribe)叙赚。
我們先給出一個簡單的實例老客,然后再分開來講觀察者(Observer)、可觀察者(Observable)震叮、訂閱(subscribe)胧砰。
import {Component} from '@angular/core';
import {of} from 'rxjs';
// 創(chuàng)建一個可觀察者對象-Observable,發(fā)射三個數(shù)據(jù)1冤荆、2、3
const myObservable = of(1, 2, 3);
// 創(chuàng)建一個觀察者對象-Observer(處理next权纤、error钓简、complete回調(diào))
const myObserver = {
next: x => console.log('Observer got a next value: ' + x),
error: err => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
// 通過Observable的subscribe函數(shù),觀察者去訂閱可觀察者的消息
myObservable.subscribe(myObserver);
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
1.1汹想、Observer(觀察者)
? ? ? ?Observer(觀察者)用于接收Observable(可觀察者)對象通知的處理器(說白了就是就是接收Observable發(fā)送過來的消息)外邓。Observer(觀察者)需要實現(xiàn)Observer接口。
觀察者對象定義了一些回調(diào)函數(shù)來處理可觀察對象可能會發(fā)來的三種通知(Observer接口里面的方法)古掏。
通知類型(方法) | 說明 |
---|---|
next | 必要损话。用來處理每個發(fā)送過來的值。在開始執(zhí)行后可能執(zhí)行零次或多次 |
error | 可選槽唾。用來處理錯誤通知丧枪。錯誤會中斷這個可觀察對象實例的執(zhí)行過程 |
complete | 可選。用來處理執(zhí)行完畢(complete)的通知庞萍。當(dāng)執(zhí)行完畢后拧烦,這些值就會繼續(xù)傳給下一個處理器 |
? ? ? ?當(dāng)然了觀察者對象可以定義這三種處理器(next、error钝计、complete)的任意組合恋博。如果你不為某種通知類型提供處理器,這個觀察者就會忽略相應(yīng)類型的通知私恬。
? ? ? ?舉個例子比如我們只想處理next()方法對應(yīng)的通知那么觀察值就可以這么寫了:
// 創(chuàng)建一個觀察者對象-Observer(只處理next回調(diào))
const myObserver = {
next: x => console.log('Observer got a next value: ' + x),
};
1.2债沮、Observable(可觀察者)
? ? ? ?使用Observable構(gòu)造函數(shù)可以創(chuàng)建任何類型的可觀察流。 當(dāng)執(zhí)行可觀察對象的subscribe()方法時本鸣,這個構(gòu)造函數(shù)就會把它接收到的參數(shù)作為訂閱函數(shù)來運行疫衩。 訂閱函數(shù)需要接收一個Observer對象,并把值發(fā)布給觀察者對象的next()方法荣德。其實很好理解隧土,比如有如下的代碼提针,sequenceSubscriber方法是Observable構(gòu)造函數(shù)的參數(shù)。當(dāng)調(diào)用subscribe()方法訂閱的時候就會執(zhí)行sequenceSubscriber方法里面的動作發(fā)射數(shù)據(jù)曹傀。
import {Component} from '@angular/core';
import {Observable, of} from 'rxjs';
// 可觀察者構(gòu)造函數(shù)的參數(shù)
function sequenceSubscriber(observer) {
// 發(fā)射三個值
observer.next(1);
observer.next(2);
observer.next(3);
observer.complete();
return {
unsubscribe() {
}
};
}
// 通過構(gòu)造函數(shù)來創(chuàng)建一個可觀察者
const sequence = new Observable(sequenceSubscriber);
// 訂閱
sequence.subscribe({
next(num) {
console.log(num);
},
complete() {
console.log('Finished sequence');
}
});
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
1.3辐脖、 Subscribing(訂閱)
? ? ? ?光有觀察者和可觀察者是不夠的,還需要通過訂閱把他兩串聯(lián)起來才能運作起來皆愉。只有當(dāng)有人訂閱Observable的實例時嗜价,它才會開始發(fā)布值。 訂閱就是去調(diào)用Observable對象的subscribe()方法幕庐,并把一個Observer對象傳給它久锥,用來接收通知。subscribe()方法的調(diào)用會返回一個Subscription對象异剥,該對象具有一個unsubscribe()方法瑟由。當(dāng)調(diào)用該方法時,你就會停止接收通知冤寿。
1.4歹苦、多播
? ? ? ?默認(rèn)情況下可觀察對象會為每一個觀察者創(chuàng)建一次新的、獨立的執(zhí)行督怜。 訂閱了多少次就會有多少個獨立的流(next監(jiān)聽器會重復(fù)調(diào)用)殴瘦。
? ? ? ?多播:多播用來讓可觀察對象在一次執(zhí)行中同時廣播給多個訂閱者。借助支持多播的可觀察對象号杠,你不必注冊多個監(jiān)聽器蚪腋,而是復(fù)用第一個(next)監(jiān)聽器,并且把值發(fā)送給各個訂閱者姨蟋。我們通過一個簡單的實例來看多播的代碼應(yīng)該怎么寫屉凯,會把所有的觀察者放在一個數(shù)組里面,然后復(fù)用第一個觀察者的監(jiān)聽器眼溶。
多播代碼
import {Component} from '@angular/core';
import {Observable} from 'rxjs';
function multicastSequenceSubscriber() {
// 需要發(fā)射的數(shù)據(jù)
const seq = [1, 2, 3];
// 觀察者數(shù)組神得,多播那肯定會有多個觀察者
const observers = [];
let timeoutId;
// 在調(diào)用Observable對應(yīng)subscriber()方法的時候,會傳入進(jìn)來observer觀察者對象
return (observer) => {
// observers觀察者對象加入數(shù)組
observers.push(observer);
// 第一次有觀察者訂閱過來的時候
if (observers.length === 1) {
timeoutId = doSequence({
next(val) {
// 遍歷每個觀察者偷仿,調(diào)用觀察者的next()方法
observers.forEach(obs => obs.next(val));
},
complete() {
// 遍歷每個觀察者哩簿,調(diào)用觀察者的complete()方法,調(diào)用slice(0)又沖第一個元素開始遍歷酝静。
// 因為前面已經(jīng)調(diào)用過observers.forEach了已經(jīng)移動到最后一個元素去了
observers.slice(0).forEach(obs => obs.complete());
}
}, seq, 0);
}
return {
unsubscribe() {
// 如果調(diào)用了取消訂閱节榜,則從數(shù)組里面刪除
observers.splice(observers.indexOf(observer), 1);
// 如果是最后一個,則清除 timer out
if (observers.length === 0) {
clearTimeout(timeoutId);
}
}
};
};
}
// 每秒發(fā)射一個數(shù)據(jù)
function doSequence(observer, arr, idx) {
return setTimeout(() => {
observer.next(arr[idx]);
if (idx === arr.length - 1) {
observer.complete();
} else {
// 繼續(xù)執(zhí)行
doSequence(observer, arr, ++idx);
}
}, 1000);
}
// 創(chuàng)建一個多播的被觀察者
const multicastSequence = new Observable(multicastSequenceSubscriber());
// 第一個觀察者訂閱
multicastSequence.subscribe({
next(num) {
console.log('1st subscribe: ' + num);
},
complete() {
console.log('1st sequence finished.');
}
});
// 1.5s之后别智,第二個觀察者訂閱
setTimeout(() => {
multicastSequence.subscribe({
next(num) {
console.log('2nd subscribe: ' + num);
},
complete() {
console.log('2nd sequence finished.');
}
});
}, 1500);
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
二宗苍、RxJS
2.1、RxJS簡單介紹
? ? ? ?RxJS(響應(yīng)式擴(kuò)展的JavaScript 版)是一個使用可觀察對象進(jìn)行響應(yīng)式編程的庫。它讓組合異步代碼和基于回調(diào)的代碼變得更簡單讳窟。RxJS文檔鏈接 https://rxjs-dev.firebaseapp.com/ 让歼。
RxJS是一個庫,一個工具丽啡,讓我們寫異步的代碼非常的簡單谋右。
? ? ? ?RxJS的學(xué)習(xí)關(guān)鍵在操作符的學(xué)習(xí),RxJS提供了各種各樣的操作符补箍。操作符用的對很多事情能事半功倍改执。操作符的類型有:創(chuàng)建操作符、組合操作符坑雅、過濾操作符辈挂、轉(zhuǎn)換操作符、多播操作符等等裹粤。
RxJS常用操作符
類別 | 操作 |
---|---|
創(chuàng)建 | from终蒂、fromPromise、fromEvent遥诉、of等 |
組合 | combineLatest拇泣、concat、merge突那、startWith挫酿、withLatestFrom构眯、zip等 |
過濾 | debounceTime , distinctUntilChanged , filter , take , takeUntil等 |
轉(zhuǎn)換 | bufferTime , concatMap , map , mergeMap , scan , switchMap等 |
工具 | tap等 |
多播 | share等 |
如果想深入的學(xué)習(xí)RxJS可以多去了解里面的操作符愕难。我只能說操作符非常的強(qiáng)大。
2.2惫霸、管道pipe
? ? ? ?有的時候我們可能想把多個操作符連接起來就需要借助管道pipe()函數(shù)來實現(xiàn)猫缭。pipe() 函數(shù)以你要組合的這些函數(shù)作為參數(shù),并且返回一個新的函數(shù)壹店,當(dāng)執(zhí)行這個新函數(shù)時猜丹,就會順序執(zhí)行那些被組合進(jìn)去的函數(shù)。我們用一個簡單的實例來來看看pipe管道怎么使用硅卢,通過管道把filter操作符和map操作符鏈接起來射窒。
要是在RxJava里面要把多個操作符鏈接起來,非常的簡單直接...鏈?zhǔn)骄幊叹涂梢詫崿F(xiàn)将塑。但是RxJS里面不支持這種操作脉顿,只能通過管道把多個操作符鏈接起來。
import {Component} from '@angular/core';
import {of} from 'rxjs';
import {filter, map} from 'rxjs/operators';
// 通過RxJS的of創(chuàng)建操作符創(chuàng)建一個Observable對象点寥,并且通過管道把filter操作符和map操作符鏈接起來
const squareOdd = of(1, 2, 3, 4, 5)
.pipe(
// 只需要奇數(shù)
filter(n => n % 2 !== 0),
// 值平方
map(n => n * n)
);
// 訂閱
squareOdd.subscribe(x => console.log(x));
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
2.3艾疟、RxJS錯誤處理
? ? ? ?RxJS除了可以在訂閱時提供error()處理器外,RxJS 還提供了catchError操作符來處理一些不是致命的錯誤。什么意思蔽莱,我們知道一旦走到error()方法去了之后整個數(shù)據(jù)流就直接斷了弟疆,比如我們順序發(fā)送100個數(shù)據(jù),第一個數(shù)據(jù)發(fā)送的時候就發(fā)生了錯誤后面的99個數(shù)據(jù)都沒辦法再發(fā)送了盗冷。RxJS里面的catchError操作符可以避免這種情況怠苔,他讓你有一個修復(fù)的機(jī)會,我們可以在catchError里面做一些特殊的處理正塌,當(dāng)?shù)谝粋€數(shù)據(jù)發(fā)送的時候的錯誤嘀略,我們可以通過某種方式讓數(shù)據(jù)可以繼續(xù)發(fā)送。 比如如下的實例乓诽,第一個數(shù)據(jù)發(fā)射的時候產(chǎn)生了錯誤帜羊,我們接著從2開始發(fā)送數(shù)據(jù)。
RxJS里面的catchError 操作符讓我們對一些錯誤可以做一些修復(fù)鸠天。
import {Component} from '@angular/core';
import {range} from 'rxjs';
import {catchError, map} from 'rxjs/operators';
const apiData = range(1, 100).pipe(
map(value => {
if (value === 1) {
// 第一個數(shù)據(jù)就發(fā)生錯誤了
throw new Error('Value expected!');
}
return value;
}),
// 當(dāng)有錯誤返回的時候讼育,我們又從2開始發(fā)送
catchError(err => range(2, 99))
);
apiData.subscribe({
next(x) {
console.log('data: ', x);
},
error(err) {
console.log('errors already caught... will not run');
}
});
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
}
三、Angular里面的可觀察對象
? ? ? ?Angular通常使用可觀察對象作為處理各種常用異步操作的接口稠集。
3.1奶段、事件發(fā)送器 EventEmitter
? ? ? ?EventEmitter類我們在前一篇文章(組件交互)有提過,當(dāng)子組件想向父組件發(fā)送消息的時候我們就用到了EventEmitter剥纷。EventEmitter用來從組件的 @Output() 屬性中發(fā)布一些值痹籍。EventEmitter擴(kuò)展了Observable,并添加了一個 emit()方法晦鞋,這樣它就可以發(fā)送任意值了蹲缠。當(dāng)你調(diào)用emit() 時,就會把所發(fā)送的值傳給訂閱上來的觀察者的next()方法悠垛。
子組件代碼
import {Component, EventEmitter, Output} from '@angular/core';
@Component({
selector: 'app-data-child',
template: `
<button (click)="vote(true)">點擊</button>
`
})
export class DataChildComponent {
// @Output定義一個準(zhǔn)備回調(diào)父組件的事件EventEmitter也是可以傳遞參數(shù)的
@Output() voted = new EventEmitter<boolean>();
vote(agreed: boolean) {
// 把事件往上拋出去线定,可以帶參數(shù)
this.voted.emit(agreed);
}
}
父組件代碼
import {Component} from '@angular/core';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<p>點擊 {{clickCount}} 次</p>
<app-data-child (voted)="onVoted($event)"></app-data-child>
`
})
export class DataParentComponent {
clickCount = 0;
/**
* 子組件拋上來的事件
*/
onVoted(agreed: boolean) {
this.clickCount++;
}
}
? ? ? ?上面代碼中我們在子組件里面通過@Output() voted = new EventEmitter<boolean>();定義了一個EventEmitter類型的輸出變量voted。并且父組件里面綁定到了這個輸出變量(其實就是訂閱的關(guān)系)确买。我們可以理解下他們是怎么工作的斤讥。當(dāng)子組件里面的按鈕被點擊之后,調(diào)用了EventEmitter的emit()函數(shù)湾趾。這個時候EventEmitter會去檢測有那些觀察值訂閱了這個EventEmitter芭商。然后調(diào)用這些訂閱者的next()函數(shù)從而調(diào)用父組件模板里面寫的函數(shù)。
3.2搀缠、HTTP
? ? ? ?Angular的HttpClient從HTTP 方法調(diào)用中返回了可觀察對象铛楣。例如,http.get('url')胡嘿、http.post('url')返回的對象就是可觀察對象蛉艾。我們還是用一個非常簡單的實例來說明。組件里面有一個按鈕,點擊這個按鈕的時候通過htt.get(url)獲取url上對應(yīng)的數(shù)據(jù)勿侯。
service對應(yīng)代碼
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
@Injectable()
export class NetworkProtocolsService {
constructor(private http: HttpClient) {
}
/**
* 獲取配置信息-返回Observable
*/
getConfig() {
return this.http.get('assets/config.json');
}
}
組件對應(yīng)代碼
import {Component} from '@angular/core';
import {NetworkProtocolsService} from '../network-protocols.service';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<button (click)="onButtonClick()">點擊獲取配置信息</button>
<p>配置信息:{{message}}</p>
`,
providers: [NetworkProtocolsService]
})
export class DataParentComponent {
message = '';
constructor(private protocolsService: NetworkProtocolsService) {
}
// 點擊按鈕獲取配置信息,簡單的把配置信息顯示出來
onButtonClick() {
this.protocolsService.getConfig()
.subscribe(
data => this.message = data.toString(),
err => console.error('network get error: ' + err));
}
}
3.3拓瞪、AsyncPipe(異步管道)
? ? ? ?AsyncPipe(異步管道)訂閱一個 Observable或Promise對象(這里我們只講Observable),并返回它發(fā)出的最新值助琐。 當(dāng)通過next()方法發(fā)出新值時祭埂,訂閱的異步管道就知道數(shù)據(jù)發(fā)送變化了,然后標(biāo)識組件需執(zhí)行變化檢測兵钮。 當(dāng)組件被銷毀時蛆橡,異步管道自動取消訂閱,以避免潛在的內(nèi)存泄漏掘譬。
? ? ? ?如下實例泰演,time是一個Observable,并且并且每一秒都發(fā)射一個新的值葱轩。
import {Component} from '@angular/core';
import {Observable} from 'rxjs';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<div><code>observable|async</code>:
Time: {{ time | async }}
</div>`
})
export class DataParentComponent {
time = new Observable(observer => {
setInterval(() => observer.next(new Date().toString()), 1000);
}
);
}
3.4睦焕、路由器(router)
? ? ? ?Router.events路由器的事件都是以可觀察對象的形式提供的。而且路由器的事件有以下七種靴拱。如下所示:
路由器事件 | 說明 |
---|---|
NavigationStart | 會在導(dǎo)航開始時觸發(fā) |
RoutesRecognized | 會在路由器解析完 URL垃喊,并識別出了相應(yīng)的路由時觸發(fā) |
RouteConfigLoadStart | 會在 Router 對一個路由配置進(jìn)行惰性加載之前觸發(fā) |
RouteConfigLoadEnd | 會在路由被惰性加載之后觸發(fā) |
NavigationEnd | 會在導(dǎo)航成功結(jié)束之后觸發(fā) |
NavigationCancel | 會在導(dǎo)航被取消之后觸發(fā)。 這可能是因為在導(dǎo)航期間某個路由守衛(wèi)返回了 false |
NavigationError | 會在導(dǎo)航由于意料之外的錯誤而失敗時觸發(fā) |
? ? ? ?因為路由器事件是以O(shè)bservable的形式提供的袜炕,我們也可以使用RxJS里面的filter()操作符來找到感興趣的事件本谜,并且訂閱它們。如下代碼我們只關(guān)心NavigationStart事件偎窘。
import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs';
import {NavigationStart, Router} from '@angular/router';
import {filter} from 'rxjs/operators';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: ``
})
export class DataParentComponent implements OnInit {
navStart: Observable<NavigationStart>;
constructor(private router: Router) {
// 我們只是關(guān)心NavigationStart事件
this.navStart = router.events.pipe(
filter(evt => evt instanceof NavigationStart)
) as Observable<NavigationStart>;
}
ngOnInit(): void {
this.navStart.subscribe(evt => console.log('Navigation Started!'));
}
}
3.5乌助、響應(yīng)式表單 (reactive forms)
? ? ? ?響應(yīng)式表單具有一些屬性,它們使用可觀察對象來監(jiān)聽表單控件的值评架。FormControl的valueChanges屬性和statusChanges屬性包含了會發(fā)出變更事件的可觀察對象眷茁。訂閱可觀察的表單控件屬性是在組件類中觸發(fā)應(yīng)用邏輯的途徑之一炕泳。
? ? ? ?用一個簡單的實例來說明纵诞,當(dāng)input里面的值變化的時候,我們能獲取input的值的變化培遵。
import {Component, OnInit} from '@angular/core';
import {FormControl, FormGroup} from '@angular/forms';
@Component({
selector: 'app-data-parent',
styleUrls: ['./data-parent.component.css'],
template: `
<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm" novalidate>
<div class="form-group">
<label class="center-block">Name:
<input class="form-control" formControlName="name">
</label>
</div>
</form>
`
})
export class DataParentComponent implements OnInit {
heroForm = new FormGroup({
name: new FormControl()
});
ngOnInit() {
const nameControl = this.heroForm.get('name');
// 當(dāng)表單里面數(shù)據(jù)變化的時候浙芙,可以收到通知。nameControl.valueChanges就是一個Observable
nameControl.valueChanges.forEach(
(value: string) => console.log(value)
);
}
}
? ? ? ?以上就是對Angular2以上Observable和RxJS的簡答介紹籽腕。最后還是想告訴大家最好的文檔還是官方文檔 https://www.angular.cn/guide/observables