RxJS

How to Create

Install via npm

npm install --save rxjs

Import into your code

import { Observable } from 'rxjs';

Creation

const source = new Observable(function subscribe(subscriber) {
    console.log('Observable created');

    let count = 0;

    const timer = setInterval(() => {
        subscriber.next(count);
        count++;
    }, 1000);

    return () => {
        console.log('Observable destroyed');
        clearInterval(timer);
    }
});

Handling

const subscription = source.subscribe(
    val => console.log('next:', val),
    err => console.error('error:', err),
    () => console.log('completed')
);

setTimeout(() => subscription.unsubscribe(), 4500);

.error()

const source = new Observable((subscriber) => {
     console.log('Observable created');
     let count = 0;

     const timer = setInterval(() => {
        if( count < 3 ) {
          subscriber.next(count++);
        } else {
          subscriber.error('Damn!');
        }
      }, 1000);

     return () => {
       console.log('Observable destroyed');
       clearInterval(timer);
     };
});

const subscription = source.subscribe(
    val => console.log('next:', val),
    err => console.error('error:', err),
    () => console.log('completed')
);

.complete()

const source = new Observable((subscriber) => {
      console.log('Observable created');
      let count = 0;

      const timer = setInterval(() => {
        if( count < 3 ) {
          subscriber.next(count++);
        } else {
          subscriber.complete();
        }
      }, 1000);

      return () => {
        console.log('Observable destroyed');
        clearInterval(timer);
      }
});

const subscription = source.subscribe(
    val => console.log('next:', val),
    err => console.error('error:', err),
    () => console.log('completed')
);

of(any)

import { of } from 'rxjs';

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

subscription = source.subscribe(
  val => console.log('next:', val),
  err => console.error('error:', err),
  () => console.log('completed')
);

from([])

import { from } from 'rxjs';

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

subscription = source.subscribe(
  val => console.log('next:', val),
  err => console.error('error:', err),
  () => console.log('completed')
);

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;

Error Handling

catchError()

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

source = throwError('Damn');

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

retry(count)

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

source = new Observable(subscriber => {
  console.log('Next attempt');
  subscriber.error('Damn!');
});

subscription = source.pipe( retry(2) )
  .subscribe(
    val => console.log('next:', val),
    err => console.error('handle error:', err),
    () => console.log('completed')
  );

retryWhen(err$ => obs)

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

flag = true;

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

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

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

onErrorResumeNext()

import { Observable, of } from 'rxjs';
import { onErrorResumeNext } from 'rxjs/operators';

source = new Observable(subscriber => {
  console.log('Attempt');
  subscriber.error('Damn!');
});

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

subscription = source.pipe( onErrorResumeNext(planB) )
  .subscribe(
    val => console.log('next:', val),
    err => console.error('handle error:', err),
    () => console.log('completed')
  );

Always think about error handling

..it happens

RxJS Marbles

Observable vs Promise

0 subscriptions

import { Observable } from 'rxjs';

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

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

1 subscription

import { Observable } from 'rxjs';

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

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


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

2 subscriptions

import { Observable } from 'rxjs';

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

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


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

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

Conclusion

1. Observable does nothing,

               till it has no subscription.

 

2. Promise runs only once.

               Then it uses calculated result.

 

3. Observable runs for each subscription.

Observable vs Array

Array

someArray = [10, 20, 30, 40];

result = someArray
  .map(item => {
    console.log('[MAP]', item);
    return item + 5;
  })
  .filter(item => {
    console.log('[FILTER]', item);
    return item > 30;
  })
  .reduce((acc, item) => acc + item);

console.log(result);

Observable

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

someArray = [10, 20, 30, 40];

from(someArray).pipe(
  map(item => {
    console.log('[MAP]', item);
    return item + 5;
  }),
  filter(item => {
    console.log('[FILTER]', item);
    return item > 30;
  })
).subscribe( val => console.log('next:', val) );

Observable with reduce()

import { from } from 'rxjs';
import { map, filter, reduce } from 'rxjs/operators';

someArray = [10, 20, 30, 40];

from(someArray).pipe(
  map(item => {
    console.log('[MAP]', item);
    return item + 5;
  }),
  filter(item => {
    console.log('[FILTER]', item);
    return item > 30;
  }),
  reduce((acc, item) => acc + item)
).subscribe( val => console.log('next:', val) );

Observable with scan()

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

someArray = [10, 20, 30, 40];

from(someArray).pipe(
  map(item => {
    console.log('[MAP]', item);
    return item + 5;
  }),
  filter(item => {
    console.log('[FILTER]', item);
    return item > 30;
  }),
  scan((acc, item) => acc + item)
).subscribe( val => console.log('next:', val) );

Conclusion

1. Array processes all values stage by stage

 

2. Within Observable  each value is processed on all stages and then we can move to the next value

 

Combine Observables

import { Observable, concat } from 'rxjs';

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

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

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

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

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

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

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

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

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

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

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

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

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

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

forkJoin(a, b).subscribe(
  val => console.log('next:', val)
);

forkJoin()

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));

switchMap()

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

...

Subject

Observable

.subscribe()

.pipe()

Observer

.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')
);

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')
);

*set on constructor buffer size (number)

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')
);

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')
);

HOT

Observable

Producer 1

Subscriber 1

Subscriber 2

Subscriber N

Subject

...

Make Observable HOT again

import { defer, of } from 'rxjs';
import { multicast } from 'rxjs/operators';

random = () => Math.floor(Math.random() * 100);

cold = defer(() => of(random()));
hot = cold.pipe( multicast(new Subject()) );

hot.subscribe(x => console.log('1:', x));
hot.subscribe(x => console.log('2:', x));

hot.connect();

.refCount()

import { defer, of } from 'rxjs';
import { multicast } from 'rxjs/operators';

random = () => Math.floor(Math.random() * 100);

cold = defer(() => of(random()));
hot = cold.pipe( multicast(new Subject()) ).refCount();

hot.subscribe(x => console.log('1:', x));
hot.subscribe(x => console.log('2:', x));

Subject factory

import { defer, of } from 'rxjs';
import { multicast } from 'rxjs/operators';

random = () => Math.floor(Math.random() * 100);

cold = defer(() => of(random()));
hot = cold.pipe(
  multicast(() => new Subject())
).refCount();

hot.subscribe(x => console.log('1:', x));
hot.subscribe(x => console.log('2:', x));

.multicast(new Subject()) === publish()

import { defer, of } from 'rxjs';
import { publish } from 'rxjs/operators';

random = () => Math.floor(Math.random() * 100);

cold = defer(() => of(random()));
hot = cold.pipe(publish());

hot.subscribe(x => console.log('1:', x));
hot.subscribe(x => console.log('2:', x));

hot.connect();

don't forget about '.connect()'

.multicast(() => new Subject()) + .refCount() === share()

import { defer, of } from 'rxjs';
import { share } from 'rxjs/operators';

random = () => Math.floor(Math.random() * 100);

cold = defer(() => of(random()));
hot = cold.pipe(share());

hot.subscribe(x => console.log('1:', x));
hot.subscribe(x => console.log('2:', x));

Common pitfalls

Nothing Happens

// Service Method
public saveSomething(data) {
  return this.http.post(someUrl, { data });
}

// Component Method
this.myService.saveSomething('Some info');
// Service Method
public saveSomething(data) {
  return this.http.post(someUrl, { data });
}

// Component Method
this.myService.saveSomething('Some info')
  .subscribe( result => 
    console.log('Success! Result:', result)
  );

Multiple Requests

// Service Method
public receiveData() {
  return this.http.get(someUrl);
}

// Component
this.data$ = this.myService.receiveData();

this.data$.subscribe( result => 
  console.log('Success! Result:', result)
);

// Component Template
<div *ngIf="(data$ | async) as data">{{ data }}</div>
... /someUrl GET 200 OK
... /someUrl GET 200 OK

Useful links

RxJS v.6 Lecture

By Anton Bely

RxJS v.6 Lecture

  • 1,420