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
- 767