rxjs

before we start...

How do you create a promise?

const promise = new Promise((resolve, reject) => {
    ... long running code
    resolve(data);
});

How do you create an observable?

const obs$ = new Observable(observer => {
    ... long running code
    observer.next(data);
    observer.next(data);
    observer.complete();
});

Observables are lazy

Compared to promises

let obs$ = new Observable(() => 
    console.log('work');
);
obs$.subscribe()
obs$.toPromise()
new Promise(() =>
    console.log('work');
);

How do you error a promise?

const promise = new Promise((resolve, reject) => {
    ... long running code
    reject('It went wrong');
});

How do you error an observable?

const obs$ = new Observable(observer => {
    ... long running code
    observer.error('It went wrong');
});

.subscribe()

obs$.subscribe(onTick, onError, onComplete);

Exercise

  • create a function which creates observable emitting a number every X ms
    • 1,2,3,4...

Solution

function interval(time) {
    return new Observable(observer => {
        let counter = 1;
        setInterval(() => 
            observer.next(counter++);
        }, time);
    });
}

How to stop observing

const subscription = interval$.subscribe(onTick);
subscription.unsubscribe();

Q: What happens?

const subscription = createInterval$(1000)
    .subscribe(val => console.log(val));
subscription.unsubscribe();
function createInterval$(time) {
    return new Observable(observer => {
        let counter = 1;
        setInterval(() => {
            console.log('XXX');
            observer.next(counter++);
        }, time);
    });
}

Answer

const subscription = createInterval$(1000)
    .subscribe(val => console.log(val));
subscription.unsubscribe();
function createInterval$(time) {
    return new Observable(observer => {
        let counter = 1;
        const interval = setInterval(() => {
            console.log('XXX');
            observer.next(counter++);
        }, time);
        return () => clearInterval(interval);
    });
}

Q: What happens?

let subscription = interval$.subscribe(onTick);
subscription = interval$.subscribe(onTick);
subscription.unsubscribe();

Other ways to create observable


  • of(10, 12, 13)
  • from([10, 12, 13])
  • from(promise)
  • fromEvent(document, 'click');
  • interval(1000);
  • empty() vs of()
  • ...

timer(delay, interval)

Q: What happens?

const obs$ = from([1,2,3,4]);
console.log('one');
obs$.subscribe(
    x => console.log(x),
    null,
    () => console.log('done')
);
console.log('two');

Operators

.pipe(...)

const derived$ = interval(1000).pipe(
    map(x => x * 2)
);
derived$.subscribe(x => console.log(x));

Q: What happens?

const interval$ = interval(1000);
const derived$ = interval$.pipe(
    map(x => x * 2)
);
interval$.subscribe(x => console.log(x));

Basic operators

take(5)

skip(3)

map(x => x*2)

filter(x => x % 2)

debounceTime(1000)

tap(console.log)

reduce((acc, curr) => ...)

[1,2,3,4,5].reduce((acc, curr) =>
    acc + curr
);

Arrays

from([1,2,3,4,5]).pipe(reduce((acc, curr) =>
    acc + curr
));

Observables

scan((acc, curr) => ...)

from([1,2,3,4,5]).pipe(scan((acc, curr) =>
    acc + curr
, 0));

distinctUntilChanged()

shareReplay(1)

Exercise

  • use basic operators to print
    • output: 13, 17, 19, 23
    • start with interval(200)
function isPrime(n) {
  for (let i = 2; i < n; i++) {
    if (n % i === 0) {
      return false;
    }
  }
  return n !== 1 && n !== 0;
}

Combining multiple observables

merge

  • combines into one observables
const intervalOne$ = interval(1000);
const intervalTwo$ = interval(500);

merge(intervalOne$, intervalTwo$).subscribe(...)

combineLatest

  • waits for all to emit
  • emit each time after
const intervalOne$ = timer(1000, 1000);
const intervalTwo$ = timer(5000, 500);

combineLatest(intervalOne$, intervalTwo$).subscribe(...)


// 4, 0 <- depends on the first one to emit
// 5, 0
// 5, 1
// 5, 2
// 6, 3
// 6, 4

forkJoin

  • waits for all to complete
  • like Promise.all
const intervalOne$ = timer(1000, 1000).pipe(take(3));
const intervalTwo$ = timer(5000, 500).pipe(take(5));

forkJoin(intervalOne$, intervalTwo$).subscribe(...)

// 2, 4

switchMap

  • switches to another observable 
interval(5000).pipe(
    switchMap(() => interval(500))
)

Exercise

  • console.log random joke every 10 seconds
  • use fetchJoke method
GET https://api.chucknorris.io/jokes/random

Subjects

What is a subject?

  • observable
  • you can make it emit anytime using next()
  • similar to defer in Q
const subject = new Subject();

subject.subscribe(x => console.log(x));

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

Special types of subjects

  • BehaviorSubject
    • has getValue() for synchronous reading of the last value
  • ReplaySubject
    • emits the last value for every new subscription

Exercise

  • create a subject
  • create a button
  • make the subject emit on every click
  • console.log on each click by listening to Subject
<button (click)="methodInClass()">
    click me
</button>

Final exercise

  • reload joke every 5 seconds or when the button is clicked
  • remember all loaded jokes (use scan)

Final exercise 2

  • create a switcher
    • has ON / OFF value
  • button for turning on
  • button for turning off
  • button for toggling

rxjs

By Martin Nuc