RxJS Essential

Andrew Zhang

2021.8.12

Category

  • Observable
  • Cold vs Hot
  • Subject
  • Operators
  • Memory Leak

Observable

Observable

  • of
  • range
  • interval
  • fromEvent
  • fromPromise

Publisher

Observer

notify

subscribe

Obserable = Publisher + Iterator
import { Observable } from 'rxjs';

const source$ = new Observable(observer => {
  observer.next(1);
  observer.next(2);
  observer.next(3);
});

source$.subscribe(item => {
  console.log(item);
});

State

Normal

Error

Complete

next

complete

error

1

2

1

2

1

2

1

2

3

import { Observable } from 'rxjs';


new Observable(observer => {
  let number = 1;
  setInterval(() => {
    observer.next(number++);
  }, 1000);
});


new Observable(observer => {
  let number = 1;
  setInterval(() => {
    if (number > 2) {
      observer.complete();
    }
    observer.next(number++);
  }, 1000);
});

new Observable(observer => {
  let number = 1;
  setInterval(() => {
    if (number > 2) {
      observer.error();
    }
    observer.next(number++);
  }, 1000);
});

Cold vs Hot

Cold Observable

Observer

Observer

Observer

Observable

Observable

Observable

Unicast

1

2

3

1

2

3

Observer A

Observer B

interval, range, of

Hot Observable

Observer

Observer

Observer

Observable

Multicast

1

2

3

2

3

Observer A

Observer B

fromPromise, fromEvent

Cold 2 Hot

import { Observable } from "rxjs";

const cold = new Observable(observer => {
  observer.next(Math.random());
});

cold.subscribe(a => console.log(a));
cold.subscribe(b => console.log(b));

const producer = Math.random();

const hot = new Observable(observer => {
  observer.next(producer);
})

hot.subscribe(a => console.log(a))
hot.subscribe(b => console.log(b))

Hot

Cold

Lazy

Lazier

Subject

Subject

import { Subject, interval } from 'rxjs';

const tick$ = interval(1000)

const subject = new Subject();
tick$.subscribe(subject);

const subscription1 = subject.subscribe(
  item => console.log(item),
);

setTimeout(() => {
  const subscription2 = subject.subscribe(
    item => console.log(item),
  )
}, 1500);
import { interval } from 'rxjs';

const tick$ = interval(1000);

const subscription1 = tick$.subscribe(
  item => console.log(item),
);

setTimeout(() => {
  const subscription2 = tick$.subscribe(
    item => console.log(item),
  );
}, 1500);
Subject = Observable + Observer

0

1

2

0

1

2

0

1

2

1

2

Subject

Cold

Observer

Observer

Observer

Cold

hot

BehaviorSubject

import { BehaviorSubject } from 'rxjs';

const subject$ = new BehaviorSubject(0);

const subscription1 = subject$.subscribe(
  item => console.log(item),
);

subject$.next(1);
subject$.next(2);

const subscription2 = subject.subscribe(
  item => console.log(item),
);

subject$.next(3);

0, 1, 2, 3

2, 3

* 4

* 2

BehaviorSubject = Subject + shareReplay(1)

cache

Operators

Basic Operators

import { interval } from 'rxjs';
import { 
  map, 
  filter, 
  take, 
  scan, 
} from 'rxjs/operators';

interval(1000).pipe(
  map(n => Math.pow(n, 2)),
  scan((acc, val) => acc + val),
  filter(v => v > 10),
  take(3),
)
import { interval } from 'rxjs';
import { 
  map, 
  filter, 
  take, 
  scan, 
} from 'rxjs/operators';

interval(1000).pipe(
  take(3),
  map(n => Math.pow(n, 2)),
  scan((acc, val) => acc + val),
  filter(v => v > 10),
);

map

scan

fitler

take

Combination

source1$

source2$

source1$.pipe(concat(source2$))

merge(source1$, source2$)

source1$.pipe(zip(source2$))

source1$.pipe(withLatestFrom(source2$))

combineLatest(source1$, source2$)

forkJoin(source1$, source2$)

import { merge, combineLatest, forkJoin } from 'rxjs';
import { concat, zip, withLatestFrom } from 'rxjs/operators';

High-order Mapping

const source$ = interval(800);

source$.pipe(
  take(3),
  map(i => 
    interval(500).pipe(take(3))
  ),
)

High-order Observable

concatMap

mergeMap

switchMap

exhaustMap

const source$ = interval(800);

source$.pipe(
  take(3),
  concatMap(i => project(i)),
)

Back Pressure

interval(200).pipe(
  throttleTime(1000),
  debounceTime(1000),
)
interval(200).pipe(
  throttleTime(1000),
)

source

throttleTime

debounceTime

Error Handle

function throwOnUnluckyNumber(num) {
  if (num === 4) {
    throw new Error('unlucky number 4');
  }
  return num;
};

const source$ = interval(500).pipe(
  map(i => throwOnUnluckyNumber(i))
);

const subscription1 = source$.pipe(
  retry(1),
  catchError(err => of(8)),
  finalize(() => console.log('error or complete'))
)

// make it hot
const subject = new Subject();
source$.subscribe(subject);

const subscription2 = subject.pipe(
  retry(1),
  catchError(err => of(8)),
  finalize(() => console.log('error or complete'))
)
Retry = Unsubscribe + Subscribe

Memory Leak

Unsubscribe

Timeline

No data stack

const source$ = new Subject();
 
let s = source$.subscribe(v => console.log(v));

source$.next("1");

s = null;

s = source$.subscribe(v => console.log(v));
source$.next("2");

s.unsubscribe();

Memory Leak

ngOnInit() {
  this.dataSub = this.getData().subscribe();
  this.cancelSub = fromEvent(
    cancelBtn, 
    'click',
  ).subscribe();
  this.rangeSub = fromEvent(
    rangeSelector, 
    'change',
  ).subscribe();
}

ngOnDestroy() {
  this.dataSub.unsubscribe();
  this.cancelSub.unsubscribe();
  this.rangeSub.unsubscribe();
}

Unsubscribe manually in time

Take, TakeUntil

Complete

$destroyer = new Subject();

ngOnInit() {
  this.dataSub = this.getData().pipe(
    takeUntil(this.destroyer$),
  ).subscribe();
  
  this.cancelSub = fromEvent(
    cancelBtn, 
    'click',
  ).pipe(
  	takeUntil(this.destroyer$),
  ).subscribe();
  
  this.rangeSub = fromEvent(
    rangeSelector, 
    'change',
  ).pipe(
    takeUntil(this.destroyer$),
  ).subscribe();
}

ngOnDestroy() {
  this.$destroyer.next();
  this.$destroyer.complete();
  this.$destroyer.unsubscribe();
}
import { Observable } from "rxjs";
import { switchMap, takeUntil } from "rxjs/operators";

declare const a: Observable<number>;
declare const b: Observable<number>;
declare const notifier: Observable<any>;

const c = a
  .pipe(
    takeUntil(notifier),
    switchMap((_) => b)
  )
  .subscribe((value) => console.log(value));

TakeUntil Leak

RxJS Essential

By Junhao Zhang

RxJS Essential

  • 29