Angular-Observable和RxJS

參考文章:介紹RxJS在Angular中的應(yīng)用

一、可觀察對(duì)象(Observable)

  • 可觀察對(duì)象支持在應(yīng)用中的發(fā)布者訂閱者之間傳遞消息斤寇。
  • 可觀察對(duì)象是聲明式的——也就是說捎稚,發(fā)布者中用于發(fā)布值的函數(shù)乐横,只有在有消費(fèi)者訂閱它之后才會(huì)執(zhí)行。
  • 可觀察對(duì)象可以發(fā)送多個(gè)任意類型的值 —— 字面量今野、消息葡公、事件。你的應(yīng)用代碼只管訂閱并消費(fèi)這些值就可以了条霜,做完之后催什,取消訂閱。無論這個(gè)流是擊鍵流蛔外、HTTP響應(yīng)流還是定時(shí)器蛆楞,對(duì)這些值進(jìn)行監(jiān)聽和停止監(jiān)聽的接口都是一樣的。

1.1基本用法和詞匯

  • 作為發(fā)布者夹厌,你創(chuàng)建一個(gè)可觀察對(duì)象(Observable)的實(shí)例豹爹,其中定義了一個(gè)訂閱者(subscriber)函數(shù)。訂閱者函數(shù)用于定義“如何獲取或生成那些要發(fā)布的值或消息”矛纹。訂閱者函數(shù)會(huì)接收一個(gè) 觀察者observer)臂聋,并把值發(fā)布觀察者next() 方法
  • 當(dāng)有消費(fèi)者調(diào)用 subscribe() 方法時(shí),這個(gè)訂閱者函數(shù)就會(huì)執(zhí)行或南。作為消費(fèi)者孩等,要執(zhí)行所創(chuàng)建的可觀察對(duì)象,并開始從中接收通知采够,你就要調(diào)用可觀察對(duì)象subscribe() 方法肄方,并傳入一個(gè)觀察者observer)。
  • 觀察者是一個(gè) JavaScript 對(duì)象蹬癌,它定義了你收到的這些消息的處理器(handler)权她。
  • subscribe() 調(diào)用會(huì)返回一個(gè) Subscription 對(duì)象,該對(duì)象具有一個(gè) unsubscribe() 方法逝薪。 當(dāng)調(diào)用該方法時(shí)隅要,你就會(huì)停止接收通知。
// 在有消費(fèi)者訂閱它之前董济,這個(gè)訂閱者函數(shù)并不會(huì)實(shí)際執(zhí)行
const locations = new Observable((observer) => {
  const {next, error} = observer;
  let watchId;

  if ('geolocation' in navigator) {
    watchId = navigator.geolocation.watchPosition(next, error);
  } else {
    error('Geolocation not available');
  }

  return {unsubscribe() { navigator.geolocation.clearWatch(watchId); }};
});

// subscribe() 調(diào)用會(huì)返回一個(gè) Subscription 對(duì)象步清,該對(duì)象具有一個(gè) unsubscribe() 方法。
// subscribe()傳入一個(gè)觀察者對(duì)象虏肾,定義了你收到的這些消息的處理器
const locationsSubscription = locations.subscribe({
  next(position) { console.log('Current Position: ', position); },
  error(msg) { console.log('Error Getting Location: ', msg); }
});

// 10 seconds后調(diào)用該方法時(shí)廓啊,你就會(huì)停止接收通知。
setTimeout(() => { locationsSubscription.unsubscribe(); }, 10000);

1.2定義觀察者observer

通知類型 說明
next 必要询微。用來處理每個(gè)送達(dá)值崖瞭。在開始執(zhí)行后可能執(zhí)行零次或多次。
error 可選撑毛。用來處理錯(cuò)誤通知书聚。錯(cuò)誤會(huì)中斷這個(gè)可觀察對(duì)象實(shí)例的執(zhí)行過程。
complete 可選藻雌。用來處理執(zhí)行完畢(complete)通知雌续。當(dāng)執(zhí)行完畢后,這些值就會(huì)繼續(xù)傳給下一個(gè)處理器胯杭。

1.3訂閱

  • 只有當(dāng)有人訂閱 Observable 的實(shí)例時(shí)驯杜,訂閱者函數(shù)才會(huì)開始發(fā)布值。

  • 訂閱時(shí)要先調(diào)用該實(shí)例subscribe() 方法做个,并把一個(gè)觀察者對(duì)象傳給subscribe()鸽心,用來接收通知滚局。

  • 使用 Observable 上定義的一些靜態(tài)方法來創(chuàng)建一些常用的簡(jiǎn)單可觀察對(duì)象

    • of(...items) —— 返回一個(gè) Observable 實(shí)例,它用<font color=red size=4>同步</font>的方式把<font color=red size=4>參數(shù)</font>
      中提供的這些值發(fā)送出來顽频。

    • from(iterable) —— 把它的參數(shù)轉(zhuǎn)換成一個(gè) Observable 實(shí)例藤肢。 該方法通常用于把一個(gè)<font color=red size=4>數(shù)組轉(zhuǎn)換</font>成一個(gè)(發(fā)送多個(gè)值的)可觀察對(duì)象。

  • 下面的例子會(huì)創(chuàng)建并訂閱一個(gè)簡(jiǎn)單的可觀察對(duì)象糯景,它的觀察者會(huì)把接收到的消息記錄到控制臺(tái)中:

// 創(chuàng)建簡(jiǎn)單的可觀察對(duì)象嘁圈,來發(fā)送3個(gè)值
const myObservable = of(1, 2, 3);

// 創(chuàng)建觀察者對(duì)象
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'),
};

// 訂閱
myObservable.subscribe(myObserver);
// Observer got a next value: 1
// Observer got a next value: 2
// Observer got a next value: 3
// Observer got a complete notification

=>前面指定預(yù)定義觀察者并訂閱它,等同如下寫法蟀淮,省略了next,error,complete
myObservable.subscribe(
  // subscribe() 方法可以接收預(yù)定義在觀察者中同一行的回調(diào)函數(shù)
  x => console.log('Observer got a next value: ' + x),
  err => console.error('Observer got an error: ' + err),
  () => console.log('Observer got a complete notification')
);

無論哪種情況最住,next 處理器都是必要的,而 errorcomplete 處理器是可選的怠惶。

  • 注意涨缚,next() 函數(shù)可以接受消息字符串、事件對(duì)象策治、數(shù)字值或各種結(jié)構(gòu)仗岖。我們把由可觀察對(duì)象發(fā)布出來的數(shù)據(jù)統(tǒng)稱為任何類型的值都可以表示為可觀察對(duì)象览妖,而這些值會(huì)被發(fā)布為一個(gè)流轧拄。

1.4創(chuàng)建可觀察對(duì)象

  • 要?jiǎng)?chuàng)建一個(gè)與前面的 of(1, 2, 3) 等價(jià)的可觀察對(duì)象,你可以這樣做:
// 訂閱者函數(shù)會(huì)接收一個(gè) Observer 對(duì)象讽膏,并把值發(fā)布給觀察者的 next() 方法檩电。
function sequenceSubscriber(observer) {
  // 同步地 發(fā)布 1, 2, and 3, 然后 complete
  observer.next(1);
  observer.next(2);
  observer.next(3);
  observer.complete();
 
  // 同步發(fā)布數(shù)據(jù),所以取消訂閱 不需要做任何事情
  return {unsubscribe() {}};
}
 
// 使用 Observable 構(gòu)造函數(shù)府树,創(chuàng)建一個(gè)新的可觀察對(duì)象俐末,
// 當(dāng)執(zhí)行可觀察對(duì)象的 subscribe() 方法時(shí),這個(gè)構(gòu)造函數(shù)就會(huì)把它接收到的參數(shù)sequenceSubscriber作為訂閱者函數(shù)來運(yùn)行奄侠。 
const sequence = new Observable(sequenceSubscriber);
 
sequence.subscribe({
  next(num) { console.log(num); },
  complete() { console.log('Finished sequence'); }
});
 
// Logs:
// 1
// 2
// 3
// Finished sequence
  • 下面的例子用來發(fā)布事件的可觀察對(duì)象:
function fromEvent(target, eventName) {
    return new Observable(
        // new Observable中傳入的訂閱者函數(shù)是用內(nèi)聯(lián)方式定義的
        // 訂閱者函數(shù)會(huì)接收一個(gè) 觀察者對(duì)象observer卓箫,并把值e發(fā)布給觀察者的 next() 方法
        (observer) => {
            const handler = (e) => observer.next(e);

            // Add the event handler to the target
            target.addEventListener(eventName, handler);

            return () => {
                // Detach the event handler from the target
                target.removeEventListener(eventName, handler);
            };
        }

    );
}


const ESC_KEY = 27;
const nameInput = document.getElementById('name') as HTMLInputElement;

const subscription = fromEvent(nameInput, 'keydown')//使用fromEvent函數(shù)來創(chuàng)建可發(fā)布 keydown 事件的可觀察對(duì)象
    .subscribe(
        // subscribe() 方法接收預(yù)定義在觀察者中同一行的next回調(diào)函數(shù)
        (e: KeyboardEvent) => {
            if (e.keyCode === ESC_KEY) {
                nameInput.value = '';
            }
        }
    );

1.5多播?

1.6錯(cuò)誤處理

  • 由于可觀察對(duì)象可以setTimeout異步生成值,所以用 try/catch無法捕獲錯(cuò)誤的垄潮。你應(yīng)該在觀察者中指定一個(gè) error 回調(diào)來處理錯(cuò)誤烹卒。
  • 發(fā)生錯(cuò)誤時(shí)還會(huì)導(dǎo)致可觀察對(duì)象清理現(xiàn)有的訂閱,并且停止生成值弯洗。
  • 可觀察對(duì)象可以生成值subscribe()調(diào)用 next 回調(diào))旅急,也可以調(diào)用 completeerror 回調(diào)來主動(dòng)結(jié)束
myObservable.subscribe({
  next: (num) => console.log('Next num: ' + num),
  error: (err) => console.log('Received an errror: ' + err)
});

二牡整、RxJS 庫(kù)

RxJS是一個(gè)使用可觀察對(duì)象進(jìn)行響應(yīng)式編程的庫(kù)藐吮。

2.1創(chuàng)建可觀察對(duì)象的函數(shù)

RxJS 提供了一些用來創(chuàng)建可觀察對(duì)象函數(shù)。這些函數(shù)可以簡(jiǎn)化根據(jù)某些東西創(chuàng)建可觀察對(duì)象的過程,比如承諾谣辞、定時(shí)器迫摔、事件、ajax等等泥从。

  • 承諾
import { fromPromise } from 'rxjs';

// Create an Observable out of a promise
const data = fromPromise(fetch('/api/endpoint'));
// Subscribe to begin listening for async result
data.subscribe({
 next(response) { console.log(response); },
 error(err) { console.error('Error: ' + err); },
 complete() { console.log('Completed'); }
});
  • 定時(shí)器
import { interval } from 'rxjs';

// Create an Observable that will publish a value on an interval
const secondsCounter = interval(1000);
// Subscribe to begin publishing values
secondsCounter.subscribe(n =>
  console.log(`It's been ${n} seconds since subscribing!`));
  • 事件
import { fromEvent } from 'rxjs';

const el = document.getElementById('my-element');

// Create an Observable that will publish mouse movements
const mouseMoves = fromEvent(el, 'mousemove');

// Subscribe to start listening for mouse-move events
const subscription = mouseMoves.subscribe((evt: MouseEvent) => {
  // Log coords of mouse movements
  console.log(`Coords: ${evt.clientX} X ${evt.clientY}`);

  // When the mouse is over the upper-left of the screen,
  // unsubscribe to stop listening for mouse movements
  if (evt.clientX < 40 && evt.clientY < 40) {
    subscription.unsubscribe();
  }
});
  • ajax
import { ajax } from 'rxjs/ajax';

// Create an Observable that will create an AJAX request
const apiData = ajax('/api/data');
// Subscribe to create the request
apiData.subscribe(res => console.log(res.status, res.response));

2.2常用操作符

操作符會(huì)觀察來源可觀察對(duì)象中發(fā)出的攒菠,轉(zhuǎn)換它們,并返回由轉(zhuǎn)換后的值組成的新的可觀察對(duì)象歉闰。

  • Observable可以鏈?zhǔn)綄懛ǎ@意味著我們可以這樣:
Observable.fromEvent(node, 'input')
  .map((event: any) => event.target.value)
  .filter(value => value.length >= 2)
  .subscribe(value => { console.log(value); });

下面是整個(gè)順序步驟:

  1. 假設(shè)用戶輸入:a

  2. Observable對(duì)觸發(fā) oninput 事件作出反應(yīng)卓起,將值以參數(shù)的形式傳遞給observernext()和敬。(內(nèi)部實(shí)現(xiàn))

  3. map() 根據(jù) event.target.value 的內(nèi)容返回一個(gè)新的 Observable,并調(diào)用 next() 傳遞給下一個(gè)observer戏阅。

  4. filter() 如果值長(zhǎng)度 >=2 的話昼弟,則返回一個(gè)新的 Observable,并調(diào)用 next() 傳遞給下一個(gè)observer奕筐。

  5. 最后舱痘,將結(jié)果傳遞給 subscribe 訂閱塊。

只要記住每一次 operator 都會(huì)返回一個(gè)Observable离赫,不管 operator 有多少個(gè)芭逝,最終只有最后一個(gè) Observable 會(huì)被訂閱

  • 提倡使用管道來組合操作符渊胸,而不是使用鏈?zhǔn)綄懛?/li>
import { filter, map } from 'rxjs/operators';

const squareOdd = of(1, 2, 3, 4, 5) // 可觀察對(duì)象
  .pipe(
    filter(n => n % 2 !== 0),
    map(n => n * n)
  );

// Subscribe to get values
squareOdd.subscribe(x => console.log(x));
  • takeWhile

如果組件有多個(gè)訂閱者的話旬盯,我們需要將這些訂閱者存儲(chǔ)在數(shù)組中,當(dāng)組件被銷毀時(shí)再逐個(gè)取消訂閱翎猛。但胖翰,我們有更好的辦法:
使用 takeWhile() operator,它會(huì)在你傳遞一個(gè)布爾值是調(diào)用 next() 還是 complete()切厘。

private alive: boolean = true;
ngOnInit() {
  const node = document.querySelector('input[type=text]');

  this.s = Observable.fromEvent(node, 'input')
    .takeWhile(() => this.alive)
    .map((event: any) => event.target.value)
    .filter(value => value.length >= 2)
    .subscribe(value => { console.log(value) });
}

ngOnDestroy() {
  this.alive = false;
}

RxJS很火很大原因我認(rèn)還是提供了豐富的API萨咳,以下是摘抄:

創(chuàng)建數(shù)據(jù)流:

  • 單值:of, empty, never
  • 多值:from
    • .from([1, 2, 3, 4])
  • 定時(shí):interval, timer
  • 從事件創(chuàng)建:fromEvent
  • Promise創(chuàng)建:fromPromise
  • 自定義創(chuàng)建:create

轉(zhuǎn)換操作:

  • 改變數(shù)據(jù)形態(tài):map, mapTo, pluck
    • mapTo: event$.mapTo(1) // 使event流的值為1
    • pluck: event$.pluck('target', 'value') // 從event流中取得其target屬性的value屬性
  • 過濾一些值:filter, skip, first, last, take,distinctUntilChanged
    • distinctUntilChanged:保留跟前一個(gè)元素不一樣的元素
  • 時(shí)間軸上的操作:delay, timeout, throttletime, throttle, debouncetime, debounce, audit, bufferTime
    • throttletime
      兩個(gè)輸出的流之間間隔設(shè)置的參數(shù)時(shí)間
    • debouncetime
      數(shù)據(jù)一個(gè)接一個(gè)流過來,只要每個(gè)數(shù)據(jù)之間的間隔時(shí)間小于等于設(shè)置的參數(shù)時(shí)間疫稿,這些數(shù)據(jù)都會(huì)被攔下來培他。一個(gè)數(shù)據(jù)如果想要通過的話,它和它后面的數(shù)據(jù)間隔的時(shí)間遗座,要大于設(shè)置的參數(shù)時(shí)間
    • debounce:如果在900毫秒內(nèi)沒有新事件產(chǎn)生礁击,那么之前的事件將通過;如果在900毫秒內(nèi)有新事件產(chǎn)生契耿,那么之前的事件將被舍棄犀盟。
    • throttle:在一定時(shí)間范圍內(nèi)不管產(chǎn)生了多少事件,它只放第一個(gè)過去碎绎,剩下的都將舍棄
    • butterTime:緩存參數(shù)毫秒內(nèi)的所有的源Observable的值螃壤,然后一次性以數(shù)組的形式發(fā)出
  • 累加:reduce, scan
  • 異常處理:throw, catch, retry, finally
  • 條件執(zhí)行:takeUntil, delayWhen, retryWhen, subscribeOn, ObserveOn
  • 轉(zhuǎn)接:switch

組合數(shù)據(jù)流:

  • concat抗果,保持原來的序列順序連接兩個(gè)數(shù)據(jù)流。只有運(yùn)行完前面的流奸晴,才會(huì)運(yùn)行后面的流
  • merge冤馏,將兩個(gè)流按各自的順序疊加成一個(gè)流
  • race,預(yù)設(shè)條件為其中一個(gè)數(shù)據(jù)流完成
  • forkJoin寄啼,預(yù)設(shè)條件為所有數(shù)據(jù)流都完成
  • zip逮光,取各來源數(shù)據(jù)流最后一個(gè)值合并為對(duì)象
  • combineLatest,取各來源數(shù)據(jù)流最后一個(gè)值合并為數(shù)組
  • startWith墩划,先發(fā)出作為startWith參數(shù)指定的項(xiàng)涕刚,然后再發(fā)出由源 Observable 所發(fā)出的項(xiàng)

竊聽:

  • do、tap 是兩個(gè)完全相同的操作符乙帮,用于竊聽Observable的生命周期事件杜漠,而不會(huì)產(chǎn)生打擾。

操作符參考資料
Rxjs 常用操作符

2.3錯(cuò)誤處理

除了可以在訂閱時(shí)提供 error() 處理器外察净,RxJS 還提供了 catchError 操作符驾茴,它允許你在管道中處理已知錯(cuò)誤。
下面是使用 catchError 操作符實(shí)現(xiàn)這種效果的例子:

import { ajax } from 'rxjs/ajax';
import { map, catchError } from 'rxjs/operators';
// Return "response" from the API. If an error happens,
// return an empty array.
const apiData = ajax('/api/data').pipe(
  map(res => {
    if (!res.response) {
      throw new Error('Value expected!');
    }
    return res.response;
  }),
  //如果你捕獲這個(gè)錯(cuò)誤并提供了一個(gè)默認(rèn)值氢卡,流就會(huì)繼續(xù)處理這些值锈至,而不會(huì)報(bào)錯(cuò)。
  catchError(err => of([]))
);

apiData.subscribe({
  next(x) { console.log('data: ', x); },
  error(err) { console.log('errors already caught... will not run'); }
});

2.4重試失敗的可觀察對(duì)象

可以在 catchError 之前使用 retry 操作符译秦。
下列代碼為前面的例子加上了捕獲錯(cuò)誤前重發(fā)請(qǐng)求的邏輯:

import { ajax } from 'rxjs/ajax';
import { map, retry, catchError } from 'rxjs/operators';

const apiData = ajax('/api/data').pipe(
  retry(3), // Retry up to 3 times before failing
  map(res => {
    if (!res.response) {
      throw new Error('Value expected!');
    }
    return res.response;
  }),
  catchError(err => of([]))
);

apiData.subscribe({
  next(x) { console.log('data: ', x); },
  error(err) { console.log('errors already caught... will not run'); }
});

2.5可觀察對(duì)象的命名約定

習(xí)慣上的可觀察對(duì)象的名字以$符號(hào)結(jié)尾裹赴。

stopwatchValue$: Observable<number>;

三、Angular 中的可觀察對(duì)象

Angular 使用可觀察對(duì)象作為處理各種常用異步操作的接口诀浪。比如:

  • EventEmitter 類派生自 Observable棋返。
  • HTTP 模塊使用可觀察對(duì)象來處理 AJAX 請(qǐng)求和響應(yīng)。
  • 路由器和表單模塊使用可觀察對(duì)象來監(jiān)聽對(duì)用戶輸入事件的響應(yīng)雷猪。

3.1事件發(fā)送器 EventEmitter

Angular 提供了一個(gè) EventEmitter 類睛竣,它用來從組件的 @Output() 屬性中發(fā)布一些值。EventEmitter 擴(kuò)展Observable求摇,并添加了一個(gè) emit() 方法射沟,這樣它就可以發(fā)送任意值了。當(dāng)你調(diào)用 emit() 時(shí)与境,就會(huì)把所發(fā)送的值傳給訂閱上來的觀察者的 next() 方法验夯。

@Output() changed = new EventEmitter<string>();

click() {
    this.changed.emit('hi~');
}
@Component({
  template: `<comp (changed)="subscribe($event)"></comp>`
})
export class HomeComponent {
  subscribe(message: string) {
     // 接收:hi~
  }
}

3.2HTTP

AngularHttpClientHTTP 方法調(diào)用中返回可觀察對(duì)象。例如摔刁,http.get(‘/api’) 就會(huì)返回可觀察對(duì)象挥转。

相對(duì)于基于承諾(Promise)的 HTTP API,它有一系列優(yōu)點(diǎn):

  • 可觀察對(duì)象不會(huì)修改服務(wù)器的響應(yīng)(和在承諾上串聯(lián)起來的 .then() 調(diào)用一樣)。反之绑谣,你可以使用一系列操作符來按需轉(zhuǎn)換這些值党窜。
  • HTTP 請(qǐng)求是可以通過 unsubscribe() 方法來取消的。
  • 請(qǐng)求可以進(jìn)行配置借宵,以獲取進(jìn)度事件的變化幌衣。
  • 失敗的請(qǐng)求很容易重試

3.3Async 管道

AsyncPipe 會(huì)訂閱一個(gè)可觀察對(duì)象或承諾壤玫,并返回其發(fā)出的最后一個(gè)值豁护。當(dāng)發(fā)出新值時(shí),該管道就會(huì)把這個(gè)組件標(biāo)記為需要進(jìn)行變更檢查的

3.4路由器 (router)

  • Router.events 以可觀察對(duì)象的形式提供了其事件欲间。 你可以使用 RxJS 中的 filter() 操作符來找到感興趣的事件楚里,并且訂閱它們,以便根據(jù)瀏覽過程中產(chǎn)生的事件序列作出決定括改。 例子如下:
import { Router, NavigationStart } from '@angular/router';
import { filter } from 'rxjs/operators';

@Component({
  selector: 'app-routable',
  templateUrl: './routable.component.html',
  styleUrls: ['./routable.component.css']
})
export class Routable1Component implements OnInit {

  navStart: Observable<NavigationStart>;

  constructor(private router: Router) {
    // Create a new Observable the publishes only the NavigationStart event
    this.navStart = router.events.pipe(
      filter(evt => evt instanceof NavigationStart)
    ) as Observable<NavigationStart>;
  }

  ngOnInit() {
    this.navStart.subscribe(evt => console.log('Navigation Started!'));
  }
}
  • ActivatedRoute 是一個(gè)可注入的路由器服務(wù),它使用可觀察對(duì)象來獲取關(guān)于路由路徑路由參數(shù)的信息家坎。比如嘱能,ActivateRoute.url 包含一個(gè)用于匯報(bào)路由路徑的可觀察對(duì)象。例子如下:
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-routable',
  templateUrl: './routable.component.html',
  styleUrls: ['./routable.component.css']
})
export class Routable2Component implements OnInit {
  constructor(private activatedRoute: ActivatedRoute) {}

  ngOnInit() {
    this.activatedRoute.url
      .subscribe(url => console.log('The URL changed to: ' + url));
  }
}

3.5響應(yīng)式表單 (reactive forms)

響應(yīng)式表單具有一些屬性虱疏,它們使用可觀察對(duì)象來監(jiān)聽表單控件的值惹骂。 FormControlvalueChanges 屬性和 statusChanges 屬性包含了會(huì)發(fā)出變更事件可觀察對(duì)象。訂閱可觀察的表單控件屬性是在組件類中觸發(fā)應(yīng)用邏輯的途徑之一做瞪。比如:

import { FormGroup } from '@angular/forms';

@Component({
  selector: 'my-component',
  template: 'MyComponent Template'
})
export class MyComponent implements OnInit {
  nameChangeLog: string[] = [];
  heroForm: FormGroup;

  ngOnInit() {
    this.logNameChange();
  }
  logNameChange() {
    const nameControl = this.heroForm.get('name');
    nameControl.valueChanges.subscribe(
      (value: string) => this.nameChangeLog.push(value)
    );
  }
}

四对粪、可觀察對(duì)象與其它技術(shù)的比較

4.1可觀察對(duì)象 vs. 承諾

可觀察對(duì)象 承諾 Observable優(yōu)勢(shì)
可觀察對(duì)象是聲明式的,在被訂閱之前装蓬,它不會(huì)開始執(zhí)行著拭。 承諾是在創(chuàng)建時(shí)就立即執(zhí)行的。 這讓可觀察對(duì)象可用于定義那些應(yīng)該按需執(zhí)行的情景牍帚。
可觀察對(duì)象能提供多個(gè)值儡遮。 承諾只提供一個(gè) 這讓可觀察對(duì)象可用于隨著時(shí)間的推移獲取多個(gè)值暗赶。
可觀察對(duì)象會(huì)區(qū)分串聯(lián)處理和訂閱語(yǔ)句鄙币。 承諾只有 .then() 語(yǔ)句。 這讓可觀察對(duì)象可用于創(chuàng)建供系統(tǒng)的其它部分使用而不希望立即執(zhí)行的復(fù)雜菜譜蹂随。
可觀察對(duì)象的 subscribe() 會(huì)負(fù)責(zé)處理錯(cuò)誤十嘿。 承諾會(huì)把錯(cuò)誤推送給它的子承諾 這讓可觀察對(duì)象可用于進(jìn)行集中式岳锁、可預(yù)測(cè)的錯(cuò)誤處理绩衷。

4.2創(chuàng)建與訂閱

  • 在有消費(fèi)者訂閱之前,可觀察對(duì)象不會(huì)執(zhí)行。subscribe() 會(huì)執(zhí)行一次定義好的行為唇聘,并且可以再次調(diào)用它版姑。重新訂閱會(huì)導(dǎo)致重新計(jì)算這些值。
content_copy
// declare a publishing operation
new Observable((observer) => { subscriber_fn });
// initiate execution
observable.subscribe(() => {
      // observer handles notifications
    });
  • 承諾會(huì)立即執(zhí)行迟郎,并且只執(zhí)行一次剥险。當(dāng)承諾創(chuàng)建時(shí),會(huì)立即計(jì)算出結(jié)果宪肖。沒有辦法重新做一次表制。所有的 then 語(yǔ)句(訂閱)都會(huì)共享同一次計(jì)算。
content_copy
// initiate execution
new Promise((resolve, reject) => { executer_fn });
// handle return value
promise.then((value) => {
      // handle result here
    });

4.3串聯(lián)

  • 可觀察對(duì)象會(huì)區(qū)分各種轉(zhuǎn)換函數(shù)控乾,比如映射和訂閱么介。只有訂閱才會(huì)激活訂閱者函數(shù),以開始計(jì)算那些值蜕衡。
content_copy
observable.map((v) => 2*v);
  • 承諾并不區(qū)分最后的 .then() 語(yǔ)句(等價(jià)于訂閱)和中間的 .then() 語(yǔ)句(等價(jià)于映射)壤短。
content_copy
promise.then((v) => 2*v);

4.4可取消

  • 可觀察對(duì)象的訂閱是可取消的。取消訂閱會(huì)移除監(jiān)聽器慨仿,使其不再接受將來的值久脯,并通知訂閱者函數(shù)取消正在進(jìn)行的工作。
content_copy
const sub = obs.subscribe(...);
sub.unsubscribe();
  • 承諾是不可取消的镰吆。

4.5錯(cuò)誤處理

  • 可觀察對(duì)象的錯(cuò)誤處理是交給訂閱者錯(cuò)誤處理器的帘撰,并且該訂閱者會(huì)自動(dòng)取消對(duì)這個(gè)可觀察對(duì)象的訂閱。
content_copy
obs.subscribe(() => {
  throw Error('my error');
});
  • 承諾會(huì)把錯(cuò)誤推給其子承諾万皿。
content_copy
promise.then(() => {
      throw Error('my error');
});

4.6速查表

4.7可觀察對(duì)象 vs. 事件 API

4.8可觀察對(duì)象 vs. 數(shù)組

五摧找、Subject

我們?cè)趯懸粋€(gè)Service用于數(shù)據(jù)傳遞時(shí),總是使用 new Subject牢硅。

@Injectable()
export class MessageService {
    private subject = new Subject<any>();

    send(message: any) {
        this.subject.next(message);
    }

    get(): Observable<any> {
        return this.subject.asObservable();
    }
}

當(dāng)F組件需要向M組件傳遞數(shù)據(jù)時(shí)蹬耘,我們可以在F組件中使用 send()

constructor(public srv: MessageService) { }

ngOnInit() {
    this.srv.send('w s k f m?')
}

M組件只需要訂閱內(nèi)容就行:

constructor(private srv: MessageService) {}

message: any;
ngOnInit() {
    this.srv.get().subscribe((result) => {
        this.message = result;
    })
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末减余,一起剝皮案震驚了整個(gè)濱河市婆赠,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佳励,老刑警劉巖休里,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赃承,居然都是意外死亡妙黍,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門瞧剖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拭嫁,“玉大人可免,你說我怎么就攤上這事∽鲈粒” “怎么了浇借?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)怕品。 經(jīng)常有香客問我妇垢,道長(zhǎng),這世上最難降的妖魔是什么肉康? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任闯估,我火速辦了婚禮,結(jié)果婚禮上吼和,老公的妹妹穿的比我還像新娘涨薪。我一直安慰自己,他們只是感情好炫乓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布刚夺。 她就那樣靜靜地躺著,像睡著了一般末捣。 火紅的嫁衣襯著肌膚如雪侠姑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天塔粒,我揣著相機(jī)與錄音结借,去河邊找鬼筐摘。 笑死卒茬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咖熟。 我是一名探鬼主播圃酵,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼馍管!你這毒婦竟也來了郭赐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤确沸,失蹤者是張志新(化名)和其女友劉穎捌锭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體罗捎,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡观谦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了桨菜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豁状。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捉偏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泻红,到底是詐尸還是另有隱情夭禽,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布谊路,位于F島的核電站讹躯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏凶异。R本人自食惡果不足惜蜀撑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剩彬。 院中可真熱鬧酷麦,春花似錦、人聲如沸喉恋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)轻黑。三九已至糊肤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氓鄙,已是汗流浹背馆揉。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留抖拦,地道東北人升酣。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像态罪,于是被迫代替她去往敵國(guó)和親噩茄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容