Understanding
Higher Order Observables
by Gerard Sans (@gerardsans)
Google Developer Expert
Google Developer Expert
International Speaker
Spoken at 59 events in 19 countries
Blogger
Blogger
Community Leader
900
1.2K
Master of Ceremonies
Master of Ceremonies
Angular In Flip Flops
Angular Academy
Observables Review
1
1
1
Observable
Timeline
Emitted values
complete
next
next
next
Observable
1
1
#
Observable
Timeline
next
next
error
Handling Trouble
Happy Observable
// --1--1--1|
let a$ = Rx.Observable.create(observer => {
observer.next(1);
observer.next(1);
observer.next(1);
observer.complete();
});
let subscription = a$.subscribe({
next: v => l(v),
complete: () => l('|')
});
subscription.unsubscribe();
Troubled Observable
// --1--1--#
let a$ = Rx.Observable.create(observer => {
try {
observer.next(1);
observer.next(1);
throw('Ups!');
observer.complete();
}
catch(e) {
observer.error(e);
}
});
let subscription = a$.subscribe({
next: v => l(v),
complete: () => l('|'),
error: (e) => l('#')
});
Quiz time
interval(1000).pipe(
take(3),
filter(v=>v%2==0)
);
// a) --0--1--2|
// b) --0-----2|
// c) --------2|
Always subscribe
Quiz time
of(1).subscribe({
next: x => log(x),
complete: () => log('3')
});
log('2');
// a) 1 2 3
// b) 2 1 3
// c) 1 3 2
synchronous
by default
RxJS 5.5
ajax, audit, auditTime, bindCallback, bindNodeCallback, buffer, bufferCount, bufferTime, bufferToggle, bufferWhen, cache, catch, combineAll, combineLatest, concat, concatAll, concatMap, concatMapTo, count, create, debounce, debounceTime, defaultIfEmpty, defer, delay, delayWhen, dematerialize, distinct, distinctKey, distinctUntilChanged, distinctUntilKeyChanged, do, elementAt, empty, every, exhaust, exhaustMap, expand, filter, finally, find, findIndex, first, forkJoin, from, fromEvent, fromEventPattern, fromPromise, generate, groupBy, ignoreElements, interval, isEmpty, last, let, map, mapTo, materialize, max, merge, mergeAll, mergeMap, mergeMapTo, mergeScan, min, multicast, never, observeOn, of, pairwise, partition, pluck, publish, publishBehavior, publishLast, publishReplay, race, range, reduce, repeat, repeatWhen, retry, retryWhen, sample, sampleTime, scan, share, single, skip, skipLast, skipUntil, skipWhile, startWith, subscribeOn, switch, switchMap, switchMapTo, take, takeLast, takeUntil, takeWhile, throttle, throttleTime, throw, timeInterval, timeout, timeoutWith, timer, timestamp, toArray, toPromise, Utility Operators, window, windowCount, windowTime, windowToggle, windowWhen, withLatestFrom, zip, zipAll
120 operators
Marble Testing
rxjs-marbles
- Test cold Observables
- Test hot Observables
- Control values and errors
- Control time (TestScheduler)
Cold Observables
1
Cold
Cold Observables
1
subscriber1
1
1
1
1
1
subscriber2
1
1
Timeline
subscribe
subscribe
Cold Observable Example
import {marbles} from "rxjs-marbles";
describe("Cold Observables", () => {
describe("basic marbles", () => {
it("should support simple values as strings", marbles(m => {
const input = m.cold("--1--1|");
const expected = m.cold("--1--2|");
const output = input.map(x => x);
m.expect(output).toBeObservable(expected);
}));
});
});
toBeObservable Output
const input = m.cold("--1--1|");
const expected = m.cold("--1--2|");
...
Cold Observables basic marbles should support simple values as strings
Error:
Expected
{"frame":20,"notification":{"kind":"N","value":"1","hasValue":true}}
{"frame":50,"notification":{"kind":"N","value":"1","hasValue":true}}
{"frame":60,"notification":{"kind":"C","hasValue":false}}
to deep equal
{"frame":20,"notification":{"kind":"N","value":"1","hasValue":true}}
{"frame":50,"notification":{"kind":"N","value":"2","hasValue":true}}
{"frame":60,"notification":{"kind":"C","hasValue":false}}
Using Symbols
import {marbles} from "rxjs-marbles";
describe("Cold Observables", () => {
describe("basic marbles", () => {
it("should support simple values as strings", marbles(m => {
const values = { a: 1, b: 2 };
const input = m.cold("--a--a|", values);
const expected = m.cold("--b--b|", values);
const output = input.map(x => x+1);
m.expect(output).toBeObservable(expected);
}));
});
});
Error Handling
Error Handling
import {marbles} from "rxjs-marbles";
describe("Cold Observables", () => {
describe("basic marbles", () => {
it("should support custom Observables", marbles(m => {
const input = Observable.throw(new Error('Ups')));
const expected = m.cold("#", undefined, new Error('Ups'));
const output = input.map(x => x);
m.expect(output).toBeObservable(expected);
}));
});
});
Hot Observables
1
Hot
Hot Observables
1
subscriber1
1
1
1
1
subscriber2
1
Timeline
subscribe
subscribe
Hot Observable Example
import {marbles} from "rxjs-marbles";
describe("Hot Observables", () => {
describe("Subscriptions", () => {
it("should support basic subscriptions", marbles(m => {
const values = { a: 1, b: 2 };
const input = m.hot( "--a^-a|", values);
const expected = m.cold( "--b|", values);
const output = input.map(x => x+1);
m.expect(output).toBeObservable(expected);
}));
});
});
Subscription Test
import {marbles} from "rxjs-marbles";
describe("Hot Observables", () => {
describe("Subscriptions", () => {
it("should support testing subscriptions", marbles(m => {
const values = { a: 1, b: 2 };
const input = m.hot( "--a^-a|", values);
const subs = "^-!";
const expected = m.cold( "--b|", values);
const output = input.map(x => x+1);
m.expect(output).toBeObservable(expected);
m.expect(input).toHaveSubscriptions(subs);
}));
});
});
toHaveSubscriptions Output
const values = { a: 1, b: 2 };
const input = m.hot( "--a^-a|", values);
const subs = "^-!";
const expected = m.cold( "--b|", values);
...
Hot Observables Subscriptions should support complex subscriptions
Error:
Expected
{"subscribedFrame":0,"unsubscribedFrame":30}
to deep equal
{"subscribedFrame":0,"unsubscribedFrame":20}
Higher Order Observables
Main features
- Observables of Observables
-
Specific operators
- mergeMap
- switchMap
mergeMap
mergeMap
subscribe to all inner observables
1
a
output
3
10
10
10
10
projected1
projected2
a.mergeMap(v => b.map(x => v*x))
b
10
10
10
10
10
10
30
30
30
10
10
v
x
x
x
v
x
x
x
x
x
x
mergeMap
import {marbles} from "rxjs-marbles";
describe("High Order Observables", () => {
describe("mergeMap", () => {
it("should subscribe to all inner observables", marbles(m => {
const values = { a: 1, b: 3, x: 10, i: 10, j: 30 };
const a = m.cold("--a--b--|", values);
const b = m.cold("x----x----x|", values);
const expected = m.cold("--i--j-i--j-i--j|", values);
const subs = "^---------------!";
const output = a.mergeMap(v => b.map(x => v*x));
m.expect(output).toBeObservable(expected);
m.expect(a).toHaveSubscriptions(subs);
}));
});
});
switchMap
switchMap
unsubscribe from latest inner observable subscribe to new
a.switchMap(v => b.map(x => v*x))
1
a
3
10
10
10
10
projected1
projected2
b
10
10
10
10
10
10
30
30
30
output
v
x
x
x
v
x
x
x
x
x
x
switchMap
import {marbles} from "rxjs-marbles";
describe("High Order Observables", () => {
describe("switchMap", () => {
it("should unsubscribe from last inner observable", marbles(m => {
const values = { a: 1, b: 3, x: 10, i: 10, j: 30 };
const a = m.cold("--a--b--|", values);
const b = m.cold("x--x--x|", values);
const expected = m.cold("--i--j--j--j|", values);
const subs = "^-----------!";
const output = a.switchMap(v => b.map(x => v*x));
m.expect(output).toBeObservable(expected);
m.expect(a).toHaveSubscriptions(subs);
}));
});
});
Thanks
High Order Observables
By Gerard Sans
High Order Observables
In this talk I will be covering High Order Observables and how to master them. These Observables emit other Observables and require specific operators. I will explain how RxJS Core developers deal with complex Observables orchestration and how you can use this knowledge to take your RxJS skills to new heights!
- 3,835