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