Angular

RxJS

Observables

RxJS

REACTIVE EXTENSIONS LIBRARY FOR JAVASCRIPT

RxJS is a library for reactive programming using Observables, to make it easier to compose asynchronous or callback-based code.

Getting started

npm install --save rxjs
import { Observable } from 'rxjs';

1. Install via npm

2. Import into your code

Observable 

 

Able to be observed

Iterable...

Observable 

 

Preliminary demo

Rx.Observable

import { Observable } from 'rxjs';

const source = Observable.create(observer => {
  let count = 0;

  const timer = setInterval(() => {
    observer.next(count);
    count++;
  }, 500);

  return () => clearInterval(timer);
});

source.subscribe(
  value => { console.log('next: ', value); },
  error => { console.log('error: ', error); },
  () => { console.log('complete'); }
);
  • Three methods: next(), error() and complete()
  • Observable is just the function that takes an observer and returns another function
  • The returned function is called when an observable is destroyed
  • To destroy an observable: subscription.unsubscribe() & observer.complete()

Demo

Observable vs Promise

0 subscriptions

import { Observable } from 'rxjs';

promise = new Promise(resolve => {
  console.log('Create Promise');
  resolve('promise-result')
});

source = Observable.create(observer => {
  console.log('Create Observable');
  observer.next('observable-result');
});

1 subscription

import { Observable } from 'rxjs';

promise = new Promise(resolve => {
  console.log('Create Promise');
  resolve('promise-result')
});

source = Observable.create(observer => {
  console.log('Create Observable');
  observer.next('observable-result');
});


promise.then(data => console.log('handle:', data));
source.subscribe(value => console.log('handle:', value));

2 subscriptions

import { Observable } from 'rxjs';

promise = new Promise(resolve => {
  console.log('Create Promise');
  resolve('promise-result')
});

source = Observable.create(observer => {
  console.log('Create Observable');
  observer.next('observable-result');
});


promise.then(data => console.log('handle:', data));
source.subscribe(value => console.log('handle:', value));

promise.then(data => console.log('handle:', data));
source.subscribe(value => console.log('handle:', value));

Conclusion

  • Observable does nothing until it has at least one subscriber.
  • Promise runs and created only once. Then it uses a previously obtained result.
  • Observable creates producer for each subscription

Creation Operators

.of({})

import { of } from 'rxjs';

const source = of({message: 'any Object'});

const subscription = source.subscribe(
  value => console.log('next: ', value),
  error => console.error('error: ', error),
  () => console.log('complete')
);

.from([])

import { from } from 'rxjs';

const source = from([10, 20, 30]);

const subscription = source.subscribe(
  value => console.log('next: ', value),
  error => console.error('error: ', error),
  () => console.log('complete')
);

.range(start, count)

import { range } from 'rxjs';

source = range(10, 4);

.interval(period)

import { interval } from 'rxjs';
import { take } from 'rxjs/operators';

source = interval(2000)
    .pipe(
        take(2)
    );

.throwError()

import { throwError } from 'rxjs';

source = throwError('Damn!');

EMPTY

import { EMPTY } from 'rxjs';
import { defaultIfEmpty } from 'rxjs/operators';

source = EMPTY.pipe(
      defaultIfEmpty('any default value')
    );

NEVER

import { NEVER } from 'rxjs';

source = NEVER;

Demo

Error handling

.catchError()

import { throwError, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

const source = throwError('Whoops!');

const subscription = source.pipe(
    catchError(err => {
      console.log('catch:', err);
      return of('safety result');
    })
  )
  .subscribe(
    value => console.log('next: ', value),
    error => console.error('error: ', error),
    () => console.log('complete')
  );

.retry(count)

import { Observable } from 'rxjs';
import { retry } from 'rxjs/operators';

const source = Observable.create(observer => {
  console.log('Next attempt');
  observer.error('Whoops!');
});

const subscription = source.pipe(
    retry(2)
  )
  .subscribe(
    value => console.log('next: ', value),
    error => console.error('error: ', error),
    () => console.log('complete')
  );

.retryWhen(err$ => obs)

import { Observable, throwError } from 'rxjs';
import { retryWhen } from 'rxjs/operators';

let flag = true;

const source = Observable.create(observer => {
  console.log('Fake server call');
  if (flag) {
    observer.error('Damn');
    flag = false;
  } else {
    observer.next('success');
  }
});

const obs = new Observable(observer => {
  console.log('let wait a second');
  setTimeout(() => observer.next('any'), 1000);
});

const subscription = source.pipe( retryWhen(err$ => obs) )
  .subscribe( value => console.log('next:', value) );

.onErrorResumeNext()

import { Observable, throwError } from 'rxjs';
import { retryWhen } from 'rxjs/operators';

source = Observable.create(observer => {
  console.log('Attempt');
  observer.error('Whoops!');
});

planB = of('Nice solution ;)');

subscription = source.pipe( onErrorResumeNext(planB) )
  .subscribe(
    value => console.log('next: ', value),
    error => console.error('error: ', error),
    () => console.log('complete')
  );

Demo

RxJS operators

RxJS Marbles

Observable.pipe(...)

Observables combination

import { Observable, concat } from 'rxjs';

first = Observable.create(observer => {
  setTimeout(() => {
    observer.next('500ms');
    observer.complete();
  }, 500);
});

second = Observable.create(observer => {
  setTimeout(() => {
    observer.next('200ms');
    observer.complete();
  }, 200);
});

concat( first, second ).subscribe(val => 
  console.log('next:', val)
);
import { Observable, merge } from 'rxjs';

first = Observable.create(observer => {
  setTimeout(() => {
    observer.next('500ms');
    observer.complete();
  }, 500);
});

second = Observable.create(observer => {
  setTimeout(() => {
    observer.next('200ms');
    observer.complete();
  }, 200);
});

merge( first, second ).subscribe(
  val => console.log('next:', val)
);
import { Observable, zip } from 'rxjs';

nextFunction = (label, count, interval) => (observer) => {
  let i = 0;
  setInterval(() => {
    if (i < count) {
     observer.next(`[${label}]:${i}`);
     i++;
    } else {
      observer.complete();
    }
  }, interval);
}

a = Observable.create(nextFunction('A', 3, 500));
b = Observable.create(nextFunction('B', 4, 200));

zip(a, b).subscribe(val => console.log('next:', val));
import { Observable, combineLatest } from 'rxjs';

nextFunction = (label, count, interval) => (observer) => {
  let i = 0;
  setInterval(() => {
    if (i < count) {
     observer.next(`[${label}]:${i}`);
     i++;
    } else {
      observer.complete();
    }
  }, interval);
}

a = Observable.create(nextFunction('A', 3, 500));
b = Observable.create(nextFunction('B', 4, 200));

combineLatest(a, b).subscribe(
  val => console.log('next:', val)
);
import { Observable, forkJoin } from 'rxjs';

nextFunction = (label, count, interval) => (observer) => {
  let i = 0;
  setInterval(() => {
    if (i < count) {
     observer.next(`[${label}]:${i}`);
     i++;
    } else {
      observer.complete();
    }
  }, interval);
}

a = Observable.create(nextFunction('A', 3, 500));
b = Observable.create(nextFunction('B', 4, 200));

forkJoin(a, b).subscribe(
  val => console.log('next:', val)
);
import { fromEvent } from 'rxjs';
import { switchMap, mapTo } from 'rxjs/operators';

click$ = fromEvent(document, 'click');

click$.pipe(
  switchMap(() => {
    console.log('call server');
    return timer(3000)
      .pipe( mapTo('server response') );
  })
).subscribe(x => console.log(x));

HOT & COLD

COLD

Create new instance of provider for every subscription

HOT

Create new instance of producer for the first subscription*

* there are few details after complete or error calls

COLD

COLD Observable

Producer 1

Producer 2

Producer N

...

Subscriber 1

Subscriber 2

Subscriber N

...

HOT

Observable

Producer 1

Subscriber 1

Subscriber 2

Subscriber N

Subject

...

Subject

Observable

Observer

+

  • .subscribe()
  • .pipe()
  • .next()
  • .error()
  • .complete()

Subject

import { Subject } from 'rxjs';

subj = new Subject();

subj.subscribe(
  val => console.log('first next:', val),
  err => console.log('first was error:', err),
  () => console.log('first complete')
);

subj.next('Yes, I can');
subj.complete();

subj.subscribe(
  val => console.log('second next:', val),
  err => console.log('second was error:', err),
  () => console.log('second complete')
);

BehaviorSubject

import { BehaviorSubject } from 'rxjs';

subj = new BehaviorSubject('Default value');

subj.subscribe(
  val => console.log('first next:', val),
  err => console.log('first was error:', err),
  () => console.log('first complete')
);

subj.next( Math.floor((Math.random() * 100)) );
subj.complete();

subj.subscribe(
  val => console.log('second next:', val),
  err => console.log('second was error:', err),
  () => console.log('second complete')
);

ReplaySubject

import { ReplaySubject } from 'rxjs';

subj = new ReplaySubject();

subj.subscribe(
  val => console.log('first next:', val),
  err => console.log('first was error:', err),
  () => console.log('first complete')
);

subj.next( Math.floor((Math.random() * 100)) );
subj.complete();

subj.subscribe(
  val => console.log('second next:', val),
  err => console.log('second was error:', err),
  () => console.log('second complete')
);

AsyncSubject

import { AsyncSubject } from 'rxjs';

subj = new AsyncSubject();

subj.subscribe(
  val => console.log('first next:', val),
  err => console.log('first was error:', err),
  () => console.log('first complete')
);

subj.next( 1 );
subj.next( 2 );
subj.complete();

subj.subscribe(
  val => console.log('second next:', val),
  err => console.log('second was error:', err),
  () => console.log('second complete')
);

Useful links

Q & A

Angular. RxJS. Observables

By Pavel Razuvalau

Angular. RxJS. Observables

  • 866