Become reactive with RxJS

Wojciech Trawiński

Toruń, 2019

About me

JavaScript developer working at 7N for Roche company.

Passionate of Angular, RxJS, TypeScript and functional programming.

About me

JavaScript developer working at 7N for Roche company

Passionate of Angular, RxJS, TypeScript and functional programming

Owner of JavaScript everyday blog

Writer for Angular In Depth and Angular Love blogs

Reactive programming

... a declarative programming paradigm

concerned with data streams and the propagation of change.

Reactive programming

... a declarative programming paradigm

concerned with data streams and the propagation of change.

Object Oriented Programming

Reactive Programming

everything is a class

everything is a stream

RxJS

In a nutshell:

  • Reactive Extensions Library for JavaScript,
  • make it easier to compose asynchronous or callback-based code,
  • Observable is the most basic building block.

RxJS

In a nutshell:

  • Reactive Extensions Library for JavaScript,
  • make it easier to compose asynchronous or callback-based code,
  • Observable is the most basic building block.

Observable

Observer

subscribe

notifications

Observer

Types of observable notifications:

  • next,
  • error,
  • complete.

Observer

Types of observable notifications:

  • next,
  • error,
  • complete.

An observer may be interested in different kinds of notifications

and corresponding callbacks may be provided in the following ways:

Observer

Types of observable notifications:

  • next,
  • error,
  • complete.

An observer may be interested in different kinds of notifications

and corresponding callbacks may be provided in the following ways:

as object

interval(1000).subscribe({
  next: console.log,
  error: console.error,
  complete: () => console.log('completed')
});

Observer

Types of observable notifications:

  • next,
  • error,
  • complete.

An observer may be interested in different kinds of notifications

and corresponding callbacks may be provided in the following ways:

as object

interval(1000).subscribe({
  next: console.log,
  error: console.error,
  complete: () => console.log('completed')
});

as successive arguments

interval(1000).subscribe(
  console.log,
  console.error,
  () => console.log('completed')
);

Observable

A representation of any set of values

over any amount of time

Observable

A representation of any set of values

over any amount of time

RxJS provides a large number of creational operators:

  • of, from, pairs (streaming existing data),
  • interval, timer, range (generating data),
  • from, fromEvent, ajax (hooking into existing api),
  • merge, combineLatest, zip (combining existing streams).

Streaming existing data

const numbers = [1, 2, 3];

const source$ = from(numbers);

source$.subscribe(console.log);

//console output: 1, 2, 3

Streaming existing data

Generating data

const numbers = [1, 2, 3];

const source$ = from(numbers);

source$.subscribe(console.log);

//console output: 1, 2, 3
const source$ = interval(5000);

source$.subscribe(console.log);

//console output (every 5s): 0, 1, 2, 3 ...

Streaming existing data

Generating data

Hooking into existing api

const numbers = [1, 2, 3];

const source$ = from(numbers);

source$.subscribe(console.log);

//console output: 1, 2, 3
const source$ = interval(5000);

source$.subscribe(console.log);

//console output (every 5s): 0, 1, 2, 3 ...
const source$ = ajax('https://jsonplaceholder.typicode.com/users/1');

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

//console output: object for user with id = 1

Streaming existing data

Generating data

Hooking into existing api

Combining existing streams

const numbers = [1, 2, 3];

const source$ = from(numbers);

source$.subscribe(console.log);

//console output: 1, 2, 3
const source$ = interval(5000);

source$.subscribe(console.log);

//console output (every 5s): 0, 1, 2, 3 ...
const source$ = ajax('https://jsonplaceholder.typicode.com/users/1');

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

//console output: object for user with id = 1
const user1$ = ajax('https://jsonplaceholder.typicode.com/users/1');
const user2$ = ajax('https://jsonplaceholder.typicode.com/users/2');

const users$ = merge(user1$, user2$);

users$.subscribe(({ response }) => console.log(response));

//console output: objects for users with 1 and 2 id

Creating observable from scratch

import { Observable } from 'rxjs';

const myAwesomeStream$ = new Observable(observer => {
  let counter = 0;

  const interval = setInterval(() => {
    observer.next(counter++);
  }, 1000);

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

const mySubscription =  myAwesomeStream$.subscribe(console.log);

setTimeout(() => {
  mySubscription.unsubscribe();
}, 2500);

//console output: 0, 1

The callback passed to the Observable constructor will be called upon subscription with an observer passed to the subscribe method

Hot & Cold Observables

Hot & Cold Observables

Hot

Observable closes over notifications producer

…like watching a movie at the cinema:

  • single source for all observers,
  • if you’re late, you’ll miss the beginning.

Hot & Cold Observables

Hot

Cold

Observable closes over notifications producer

…like watching a movie at the cinema:

  • single source for all observers,
  • if you’re late, you’ll miss the beginning.

Notifications producer created at subscription

…like watching a movie on Netflix:

  • each observer gets own source,
  • no way to be late.

Subjects

… [multicasting] is the primary use case for Subjects in RxJS

Subjects

… [multicasting] is the primary use case for Subjects in RxJS

Subject is both Observable and Observer

import { Subject, interval } from 'rxjs';

const source$ = interval(1000);
const mySubject = new Subject();

source$.subscribe(mySubject);

mySubject.subscribe(val => console.log(`#1 ${val}`));

setTimeout(() => {
  mySubject.subscribe(val => console.log(`#2 ${val}`));
}, 5000);

cold

Subject

O1

O2

Subjects

… [multicasting] is the primary use case for Subjects in RxJS

Subject is both Observable and Observer

Types of Subjects:

•Subject,

•BehaviorSubject,

•ReplaySubject,

•AsyncSubject.

import { Subject, interval } from 'rxjs';

const source$ = interval(1000);
const mySubject = new Subject();

source$.subscribe(mySubject);

mySubject.subscribe(val => console.log(`#1 ${val}`));

setTimeout(() => {
  mySubject.subscribe(val => console.log(`#2 ${val}`));
}, 5000);

cold

Subject

O1

O2

Operators

A function which accepts a stream and returns a new observable

Operators

A function which accepts a stream and returns a new observable

corresponding to array methods

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

const numbers = [1, 2, 3, 4, 5];

const source$ = from(numbers);

const result$ = source$.pipe(
  map(number => number * 10),
  filter(number => number > 30)
);

result$.subscribe(console.log);

console output: 40, 50

Operators

A function which accepts a stream and returns a new observable

corresponding to array methods

encapsulating common logic

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

const numbers = [1, 2, 3, 4, 5];

const source$ = from(numbers);

const result$ = source$.pipe(
  map(number => number * 10),
  filter(number => number > 30)
);

result$.subscribe(console.log);

console output: 40, 50
import { interval } from 'rxjs';
import { throttleTime } from 'rxjs/operators';

const fastSource$ = interval(10);

const result$ = fastSource$.pipe(
  throttleTime(2000)
);

result$.subscribe(console.log);

Let's code!

Task description

  • you are given an endpoint (jsonplaceholder.typicode.com/users/:id) which returns a user's info for a given id,
  • typing a query into an input element results in a new search,
  • 500ms have to elapse after a user stopped typing before making an http request,
  • an http request shouldn’t be made if a query string hasn’t changed,
  • only the most recent query string is considered.
import { fromEvent } from 'rxjs';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input');

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

stream of input events

import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input').pipe(
  debounceTime(500)
);

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

stream of debounced input events

import { fromEvent } from 'rxjs';
import { debounceTime , map } from 'rxjs/operators';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input').pipe(
  debounceTime(500),
  map(event => (event.target as HTMLInputElement).value)
);

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

stream of debounced input values

import { fromEvent } from 'rxjs';
import { debounceTime, map, distinctUntilChanged } from 'rxjs/operators';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input').pipe(
  debounceTime(500),
  map(event => (event.target as HTMLInputElement).value),
  distinctUntilChanged()
);

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

stream of successive unique input values

import { of, fromEvent } from 'rxjs';
import { debounceTime, map, distinctUntilChanged, catchError } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input').pipe(
  debounceTime(500),
  map(event => (event.target as HTMLInputElement).value),
  distinctUntilChanged()
);

function userStreamFactory(id: string) {
  return ajax(`https://jsonplaceholder.typicode.com/users/${id}`).pipe(
    map(({ response }) => response),
    catchError(() => of(`No data for the ${id} id...`))
  )
}

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

stream of http response containing user data

import { of, fromEvent } from 'rxjs';
import { debounceTime, map, distinctUntilChanged, catchError, switchMap } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input').pipe(
  debounceTime(500),
  map(event => (event.target as HTMLInputElement).value),
  distinctUntilChanged(),
  switchMap(id => userStreamFactory(id))
);

function userStreamFactory(id: string) {
  return ajax(`https://jsonplaceholder.typicode.com/users/${id}`).pipe(
    map(({ response }) => response),
    catchError(() => of(`No data for the ${id} id...`))
  )
}

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

stream of user data satisfying task requirements

import { of, fromEvent } from 'rxjs';
import { debounceTime, map, distinctUntilChanged, catchError, switchMap } from 'rxjs/operators';
import { ajax } from 'rxjs/ajax';

const searchBoxEl = document.querySelector<HTMLInputElement>('#searchBox');
const userDataEl = document.querySelector<HTMLParagraphElement>('#userData');

const userInput$ = fromEvent(searchBoxEl, 'input').pipe(
  debounceTime(500),
  map(event => (event.target as HTMLInputElement).value),
  distinctUntilChanged(),
  switchMap(userStreamFactory)
);

function userStreamFactory(id: string) {
  return ajax(`https://jsonplaceholder.typicode.com/users/${id}`).pipe(
    map(({ response }) => response),
    catchError(() => of(`No data for the ${id} id...`))
  )
}

userInput$.subscribe(userData => {
  userDataEl.innerText = JSON.stringify(userData)
});

using point-free style

Thanks

for your attention!

wojtrawi@gmail.com

Become reactive with RxJS

By wojtrawi

Become reactive with RxJS

  • 761