├── 10
├── index.ts
└── readme.md
├── 11
├── index.ts
└── readme.md
├── 12
├── redux.ts
├── index.ts
└── readme.md
├── 13
├── index.ts
└── readme.md
├── 14
├── index.ts
└── readme.md
├── 15
├── index.ts
└── readme.md
├── 16
├── index.ts
└── readme.md
├── 17
├── index.ts
└── readme.md
├── 18
├── index.ts
└── readme.md
├── 19
├── index.ts
└── readme.md
├── 20
├── index.ts
└── readme.md
├── 21
├── index.ts
└── readme.md
├── 22
├── index.ts
└── readme.md
├── 02
├── raf.d.ts
├── index.ts
└── readme.md
├── 09
├── index.ts
└── readme.md
├── .editorconfig
├── 07
├── index.ts
└── readme.md
├── tsconfig.json
├── 01
├── index.ts
└── readme.md
├── package.json
├── LICENSE
├── 03
├── index.ts
└── readme.md
├── .gitignore
├── 05
├── index.ts
└── readme.md
├── 04
├── index.ts
└── readme.md
├── 06
├── index.ts
└── readme.md
├── 08
├── index.ts
└── readme.md
└── readme.md
/02/raf.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'raf';
2 |
--------------------------------------------------------------------------------
/09/index.ts:
--------------------------------------------------------------------------------
1 | // TODO: Implement redux style reducer
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = false
6 | indent_style = space
7 | indent_size = 2
--------------------------------------------------------------------------------
/10/index.ts:
--------------------------------------------------------------------------------
1 | import { empty, of } from 'rxjs';
2 | import { startWith, endWith, defaultIfEmpty } from 'rxjs/operators';
3 | /*
4 | of(1, 2, 3).pipe(
5 | startWith(-1, 0),
6 | endWith(4, 5, 6)
7 | )
8 | .subscribe(x => console.log(x));
9 | */
10 |
11 | empty().pipe(
12 | defaultIfEmpty(42),
13 | startWith(0, 1),
14 | endWith(4, 5, 6)
15 | )
16 | .subscribe(x => console.log(x));
17 |
18 |
--------------------------------------------------------------------------------
/07/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | from,
3 | of,
4 | merge
5 | } from 'rxjs';
6 | import {
7 | filter,
8 | map
9 | } from 'rxjs/operators';
10 |
11 | const obs$ = of('foo', 'foobar', 'foobarbaz');
12 | const piped$ = obs$.pipe(
13 | map(x => x.length),
14 | filter(x => { throw new Error(); }),
15 | );
16 |
17 | piped$
18 | .subscribe({
19 | next: x => console.log(`Next: ${x}`),
20 | error: err => console.log(`Next: ${err}`)
21 | });
--------------------------------------------------------------------------------
/15/index.ts:
--------------------------------------------------------------------------------
1 | import { interval, of } from 'rxjs';
2 | import { concatAll, map, mergeAll, take } from 'rxjs/operators';
3 |
4 | /*
5 | const num$ = of(
6 | of(1, 2, 3),
7 | of(4, 5, 6),
8 | of(7, 8, 9)
9 | );
10 | */
11 |
12 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
13 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
14 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
15 | const num$ = of(num1$, num2$, num3$);
16 |
17 | const result$ = num$.pipe(mergeAll(1));
18 |
19 | result$.subscribe({ next: item => console.log(item) });
20 |
21 |
--------------------------------------------------------------------------------
/14/index.ts:
--------------------------------------------------------------------------------
1 | import { concat, interval, merge, of } from 'rxjs';
2 | import { map, take } from 'rxjs/operators';
3 |
4 | /*
5 | const num1$ = of(1, 2);
6 | const num2$ = of(3, 4);
7 | const num3$ = of(5, 6);
8 |
9 | const num$ = concat(num1$, num2$, num3$);
10 | const subscription = num$.subscribe({
11 | next: item => console.log(item)
12 | });
13 | */
14 |
15 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
16 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
17 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
18 |
19 | const num$ = merge(num1$, num2$, num3$, 1);
20 | const subscription = num$.subscribe({
21 | next: item => console.log(item)
22 | });
--------------------------------------------------------------------------------
/20/index.ts:
--------------------------------------------------------------------------------
1 | import { interval, range, zip } from 'rxjs';
2 | import { skip, skipLast, skipWhile, take, takeLast, takeWhile } from 'rxjs/operators';
3 |
4 | /*
5 | const num$ = interval(500).pipe(
6 | take(3)
7 | );
8 | */
9 | /*
10 | const num$ = interval(500).pipe(
11 | takeWhile((item, index) => index < 4)
12 | );
13 | */
14 |
15 | const num$ = range(0, 5);
16 | const pair$ = zip(num$, num$.pipe(skip(1)));
17 |
18 | /*
19 | const num$ = range(0, 10).pipe(skipLast(8));
20 | */
21 |
22 | /*
23 | const num$ = range(0, 10).pipe(
24 | skipWhile((item, index) => index < 7)
25 | );
26 | */
27 |
28 | const subscription = pair$.subscribe({
29 | next: item => console.log(`Next: ${item}`),
30 | complete: () => console.log('Done')
31 | });
--------------------------------------------------------------------------------
/02/index.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import {
4 | Observable,
5 | SchedulerLike,
6 | animationFrameScheduler
7 | } from 'rxjs';
8 |
9 | // Polyfill requestAnimationFrame
10 | import * as raf from 'raf';
11 | raf.polyfill();
12 |
13 | function once(value: T, scheduler: SchedulerLike) {
14 | return new Observable(observer => {
15 | return scheduler.schedule(() => {
16 | observer.next(value);
17 | observer.complete();
18 | });
19 | });
20 | }
21 |
22 | const subscription = once(42, animationFrameScheduler)
23 | .subscribe({
24 | next:x => console.log(`Next: ${x}`),
25 | complete: () => console.log(`Complete!`)
26 | });
27 |
28 | // If we really don't want it to happen
29 | //subscription.unsubscribe();
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "downlevelIteration": true,
4 | "removeComments": true,
5 | "preserveConstEnums": true,
6 | "sourceMap": true,
7 | "strictFunctionTypes": true,
8 | "noImplicitAny": true,
9 | "noImplicitReturns": true,
10 | "noImplicitThis": true,
11 | "suppressImplicitAnyIndexErrors": true,
12 | "moduleResolution": "node",
13 | "stripInternal": false,
14 | "target": "es5",
15 | "outDir": "./.out",
16 | "lib": [
17 | "es5",
18 | "es2015.iterable",
19 | "es2015.collection",
20 | "es2015.promise",
21 | "es2015.symbol",
22 | "es2015.symbol.wellknown",
23 | "dom",
24 | ]
25 | },
26 | "formatCodeOptions": {
27 | "indentSize": 2,
28 | "tabSize": 2
29 | },
30 | "types": ["node"]
31 | }
--------------------------------------------------------------------------------
/18/index.ts:
--------------------------------------------------------------------------------
1 | import { Subject } from 'rxjs';
2 | import { distinct, distinctUntilChanged, distinctUntilKeyChanged } from 'rxjs/operators';
3 | /*
4 | const num$ = new Subject();
5 |
6 | num$
7 | .pipe(distinctUntilChanged())
8 | .subscribe({ next: item => console.log(`Distinct Item: ${item}`) });
9 |
10 | num$.next(1);
11 | num$.next(1);
12 | num$.next(2);
13 | num$.next(1);
14 | num$.next(2);
15 | num$.next(2);
16 |
17 | */
18 |
19 | interface Person { name: string }
20 |
21 | const person$ = new Subject();
22 |
23 | person$
24 | .pipe(distinctUntilKeyChanged('name'))
25 | .subscribe({ next: item => console.log(`Distinct Item: ${item.name}`) });
26 |
27 | person$.next({ name: 'Bob' });
28 | // Distinct Item: Bob
29 | person$.next({ name: 'Bob' });
30 | person$.next({ name: 'Mary' });
31 | // Distinct Item: Mary
32 | person$.next({ name: 'Mary' });
33 | person$.next({ name: 'Frank' });
34 | // Distinct Item: Frank
35 |
--------------------------------------------------------------------------------
/01/index.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from 'rxjs';
2 |
3 | let n = 0;
4 | let id: NodeJS.Timeout = null;
5 |
6 | // Create an Observable from scratch
7 | const number$ = new Observable(observer => {
8 | id = setInterval(() => {
9 | // Counter to run 5 times
10 | if (n++ < 5) {
11 | observer.next(n);
12 | } else {
13 | // When finished, clear the interval and complete the stream
14 | observer.complete();
15 | }
16 | }, 1000);
17 |
18 | return () => clearInterval(id);
19 | });
20 |
21 | // Handle only the next and completion handkers
22 | const observer = {
23 | next: (x: number) => {
24 | console.log(`Next: ${x}`);
25 | },
26 | complete: () => {
27 | console.log(`Complete!`);
28 | }
29 | };
30 |
31 | // Subscribe to the Observable with our obsercer
32 | const subscription = number$.subscribe(observer);
33 |
34 | // Terminate the sequence early if you want to
35 | // setTimeout(() => subscription.unsubscribe(), 2500);
36 |
--------------------------------------------------------------------------------
/17/index.ts:
--------------------------------------------------------------------------------
1 | import { fromEvent } from 'rxjs';
2 | import { mergeMap, switchMap } from 'rxjs/operators';
3 | import axios from 'axios';
4 | import { EventEmitter } from 'events';
5 |
6 | async function searchWikipedia(term: string) {//: Promise> {
7 | const response = await axios.get('http://en.wikipedia.org/w/api.php', {
8 | params: {
9 | action: 'opensearch',
10 | format: 'json',
11 | search: term
12 | }
13 | });
14 | return response.data[1];
15 | }
16 |
17 | const eventEmitter = new EventEmitter();
18 | const value$ = fromEvent(eventEmitter, 'data');
19 |
20 | const result$ = value$.pipe(
21 | switchMap(searchWikipedia)
22 | );
23 |
24 | result$.subscribe({
25 | next: item => console.log(`First match ${item[0]}`)
26 | });
27 |
28 | eventEmitter.emit('data', 'reacti');
29 | eventEmitter.emit('data', 'reactive');
30 | eventEmitter.emit('data', 'react');
31 | eventEmitter.emit('data', 'foo');
32 | eventEmitter.emit('data', 'bar');
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rxjs-advent-2018",
3 | "version": "1.0.0",
4 | "description": "RxJS Advent Calendar for 2018",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/mattpodwysocki/rxjs-advent-2018.git"
12 | },
13 | "keywords": [
14 | "RxJS",
15 | "Advent",
16 | "Reactive",
17 | "Reactive-Programming"
18 | ],
19 | "author": "Matthew Podwysocki",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/mattpodwysocki/rxjs-advent-2018/issues"
23 | },
24 | "homepage": "https://github.com/mattpodwysocki/rxjs-advent-2018#readme",
25 | "devDependencies": {
26 | "@types/node": "^10.12.12",
27 | "ts-node": "^7.0.1",
28 | "typescript": "^3.2.1"
29 | },
30 | "dependencies": {
31 | "axios": "^0.18.0",
32 | "raf": "^3.4.1",
33 | "redux": "^4.0.1",
34 | "rxjs": "^6.3.3"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/12/redux.ts:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | enum ActionType {
4 | Increment,
5 | Decrement
6 | }
7 |
8 | interface ReduxAction {
9 | type: ActionType;
10 | }
11 |
12 | interface ReduxState {
13 | value: number;
14 | }
15 | const INITIAL_STATE = { value: 0 };
16 |
17 | function counter(state: ReduxState = INITIAL_STATE, action: ReduxAction) {
18 | switch (action.type) {
19 | case ActionType.Increment:
20 | return Object.freeze({ value: state.value + 1 });
21 | case ActionType.Decrement:
22 | return Object.freeze({ value: state.value - 1 });
23 | default:
24 | return state;
25 | }
26 | }
27 |
28 | const store = createStore(counter);
29 |
30 | function render() {
31 | console.log(`Rendering changes with ${JSON.stringify(store.getState())}`);
32 | }
33 |
34 | render();
35 | const unsubscribe = store.subscribe(render);
36 |
37 | // Increment
38 | store.dispatch({ type: ActionType.Increment });
39 |
40 | // Decrement
41 | store.dispatch({ type: ActionType.Decrement });
--------------------------------------------------------------------------------
/13/index.ts:
--------------------------------------------------------------------------------
1 | import { combineLatest, merge, interval, of, zip } from 'rxjs';
2 | import { map, take, withLatestFrom } from 'rxjs/operators';
3 |
4 | /*
5 | const num1$ = of(1, 2, 3);
6 | const num2$ = of('foo', 'bar', 'baz');
7 |
8 | const num$ = zip(num1$, num2$);
9 |
10 | num$.subscribe({ next: x => console.log(`Data: ${x}`) });
11 | */
12 | /*
13 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
14 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
15 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
16 |
17 | const num$ = combineLatest(num1$, num2$, num3$);
18 | num$.subscribe({ next: x => console.log(`${x}`) });
19 | */
20 |
21 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
22 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
23 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
24 |
25 | const num$ = num1$.pipe(withLatestFrom(num2$, num3$));
26 | num$.subscribe({ next: x => console.log(`${x}`) });
--------------------------------------------------------------------------------
/19/index.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 | import { fromEvent } from 'rxjs';
3 | import { throttleTime, debounceTime } from 'rxjs/operators';
4 |
5 | const emitter = new EventEmitter();
6 |
7 | function throttle(delay: number, fn: Function) {
8 | let lastCall = 0;
9 | return function (...args: any[]) {
10 | const now = +new Date();
11 | if (now - lastCall < delay) {
12 | return;
13 | }
14 | lastCall = now;
15 | return fn(...args);
16 | }
17 | }
18 |
19 | function debounce(delay: number, fn: Function) {
20 | let timerId: NodeJS.Timeout;
21 | return function debounceFunction(...args: any[]) {
22 | if (timerId) {
23 | clearTimeout(timerId);
24 | }
25 | timerId = setTimeout(() => {
26 | fn(...args);
27 | timerId = null;
28 | }, delay);
29 | }
30 | }
31 |
32 | const event$ = fromEvent(emitter, 'data')
33 | .pipe(debounceTime(500));
34 |
35 | const subscription = event$.subscribe({
36 | next: item => console.log(`Next: ${item}`)
37 | });
38 |
39 | emitter.emit('data', 'foo');
40 | emitter.emit('data', 'bar');
41 | emitter.emit('data', 'baz');
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Matthew Podwysocki
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/03/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | asapScheduler,
3 | of,
4 | range,
5 | generate
6 | } from 'rxjs';
7 |
8 | // Using no scheduler
9 | const source1$ = of(1, 2, 3);
10 |
11 | // Using the ASAP scheduler
12 | const source2$ = of(1, 2, 3, asapScheduler);
13 |
14 | const observer = (id: number) => {
15 | return {
16 | next: (x: number) => console.log(`ID ${id} Next: ${x}`),
17 | complete: () => console.log(`ID ${id} Complete!`)
18 | };
19 | }
20 |
21 | // const subscription1 = source1$.subscribe(observer(1));
22 | // const subscription2 = source2$.subscribe(observer(2));
23 |
24 | // Using no scheduler
25 | const range1$ = range(0, 3);
26 |
27 | // Using the ASAP scheduler
28 | const range2$ = range(3, 3);
29 |
30 | // const subscription3 = range1$.subscribe(observer(1));
31 | // const subscription4 = range2$.subscribe(observer(2));
32 |
33 | // Create generated sequence
34 | const number$ = generate(
35 | 43,
36 | i => i < 46,
37 | i => i + 1,
38 | i => String.fromCharCode(i)
39 | );
40 |
41 | const subscription = number$.subscribe({
42 | next: x => console.log(`Next: ${x}`),
43 | complete: () => console.log(`Complete!`)
44 | });
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/16/index.ts:
--------------------------------------------------------------------------------
1 | import { from, fromEvent, of } from 'rxjs';
2 | import { concatMap, delay, map, mergeMap } from 'rxjs/operators';
3 | import axios from 'axios';
4 | import { EventEmitter } from 'events';
5 |
6 | /*
7 | const num$ = of(1, 2, 3).pipe(
8 | concatMap(item => of(item).pipe(
9 | delay(item * 1000),
10 | map(item => `Item ${item} delayed by ${item * 1000} ms`)
11 | ))
12 | );
13 |
14 | const subscription = num$.subscribe({
15 | next: item => console.log(item)
16 | });
17 | */
18 |
19 | async function searchWikipedia(term: string): Promise> {
20 | const response = await axios.get('http://en.wikipedia.org/w/api.php', {
21 | params: {
22 | action: 'opensearch',
23 | format: 'json',
24 | search: term
25 | }
26 | });
27 | return response.data[1];
28 | }
29 |
30 | const eventEmitter = new EventEmitter();
31 | const value$ = fromEvent(eventEmitter, 'data');
32 |
33 | const result$ = value$.pipe(
34 | mergeMap(searchWikipedia)
35 | );
36 |
37 | result$.subscribe({
38 | next: item => console.log(`First match ${item[0]}`)
39 | });
40 |
41 | eventEmitter.emit('data', 'react');
42 | eventEmitter.emit('data', 'reacti');
43 | eventEmitter.emit('data', 'reactive');
--------------------------------------------------------------------------------
/12/index.ts:
--------------------------------------------------------------------------------
1 | import { Subject } from 'rxjs';
2 | import { scan, startWith } from 'rxjs/operators';
3 |
4 | enum ActionType {
5 | Increment,
6 | Decrement,
7 | Default
8 | }
9 |
10 | interface ReduxAction {
11 | type: ActionType;
12 | }
13 |
14 | interface ReduxState {
15 | value: number;
16 | }
17 | const INITIAL_STATE = { value: 0 };
18 |
19 | function counter(state: ReduxState = INITIAL_STATE, action: ReduxAction = { type: ActionType.Default }) {
20 | switch (action.type) {
21 | case ActionType.Increment:
22 | return Object.freeze({ value: state.value + 1 });
23 | case ActionType.Decrement:
24 | return Object.freeze({ value: state.value - 1 });
25 | default:
26 | return state;
27 | }
28 | }
29 |
30 | const store$ = new Subject();
31 | const action$ = store$.pipe(
32 | scan(counter, INITIAL_STATE),
33 | startWith(INITIAL_STATE)
34 | );
35 |
36 | const unsubscribe = action$.subscribe({ next: render });
37 |
38 | function render(state: ReduxState = INITIAL_STATE) {
39 | console.log(`Rendering changes with ${state.value}`);
40 | }
41 |
42 | // Increment
43 | store$.next({ type: ActionType.Increment });
44 |
45 | // Decrement
46 | store$.next({ type: ActionType.Decrement });
--------------------------------------------------------------------------------
/05/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | from,
3 | asapScheduler,
4 | Observable
5 | } from 'rxjs';
6 | import { map } from 'rxjs/operators';
7 |
8 | /*
9 | const set = new Set([1, 2, 3]);
10 | const set$ = from(set);
11 |
12 | const subscription = set$.subscribe({
13 | next: x => console.log(`Next: ${x}`),
14 | complete: () => console.log(`Complete!`)
15 | });
16 | */
17 |
18 | /*
19 | const arrayLike = { length: 3 };
20 | const array$ = from(arrayLike)
21 | .pipe(map((_, i) => i));
22 |
23 | const subscription = array$.subscribe({
24 | next: x => console.log(`Next: ${x}`),
25 | complete: () => console.log(`Complete!`)
26 | });
27 |
28 | */
29 |
30 | /*
31 | const array = [1, 2, 3];
32 | const array$ = from (array, asapScheduler);
33 | array$.subscribe({
34 | next: x => console.log(`Next: ${x}`),
35 | complete: () => console.log('Complete')
36 | });
37 | */
38 |
39 | const iterable = function* () {
40 | yield 1;
41 | yield 2;
42 | yield 3;
43 | };
44 |
45 | const iterable$ = from(iterable(), asapScheduler);
46 | iterable$.subscribe({
47 | next: x => console.log(`Next: ${x}`),
48 | complete: () => console.log('Complete')
49 | });
50 |
51 | /*
52 | const promise = Promise.resolve(42);
53 | const promise$ = from(promise);
54 | promise$.subscribe({
55 | next: x => console.log(`Next: ${x}`),
56 | complete: () => console.log('Complete')
57 | });
58 | */
59 |
60 | /*
61 | const obs$ = new Observable(observer => {
62 | observer.next(42);
63 | observer.complete();
64 | });
65 | obs$.subscribe({
66 | next: x => console.log(`Next: ${x}`),
67 | complete: () => console.log('Complete')
68 | });
69 | */
--------------------------------------------------------------------------------
/04/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | import {
3 | timer
4 | } from 'rxjs';
5 |
6 | // Schedule something once after half a second
7 | const delay = 500;
8 | const poll$ = timer(delay);
9 |
10 | poll$.subscribe({
11 | next: (x: number) => console.log(`Delayed by ${delay}ms item: ${x}`),
12 | complete: () => console.log('Delayed complete!')
13 | });
14 | */
15 |
16 | /*
17 | import {
18 | timer
19 | } from 'rxjs';
20 |
21 | // Schedule something once after half a second
22 | const delay = 500;
23 | const period = 1000;
24 | const poll$ = timer(delay, period);
25 |
26 | const subscription = poll$.subscribe({
27 | next: (x: number) => console.log(`Delayed by ${delay}ms item: ${x}`),
28 | complete: () => console.log('Delayed complete!')
29 | });
30 |
31 | // Unsubscribe after 4 seconds
32 | setTimeout(() => subscription.unsubscribe(), 4000);
33 | */
34 |
35 | /*
36 | import { timer } from 'rxjs';
37 | import { take } from 'rxjs/operators';
38 |
39 | // Schedule something once after half a second
40 | const delay = 500;
41 | const period = 1000;
42 | const poll$ = timer(delay, period);
43 |
44 | poll$
45 | .pipe(take(4))
46 | .subscribe({
47 | next: (x: number) => console.log(`Delayed by ${delay}ms item: ${x}`),
48 | complete: () => console.log('Delayed complete!')
49 | });
50 | */
51 |
52 | import { interval } from 'rxjs';
53 | import { take } from 'rxjs/operators';
54 |
55 | // Schedule something once after half a second
56 | const period = 1000;
57 | const poll$ = interval(period);
58 |
59 | poll$
60 | .pipe(take(4))
61 | .subscribe({
62 | next: (x: number) => console.log(`Delayed by ${period}ms item: ${x}`),
63 | complete: () => console.log('Delayed complete!')
64 | });
--------------------------------------------------------------------------------
/06/index.ts:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 | import {
3 | empty,
4 | fromEvent,
5 | fromEventPattern,
6 | merge,
7 | never,
8 | throwError
9 | } from 'rxjs';
10 | import {
11 | mergeMap
12 | } from 'rxjs/operators';
13 |
14 | const emitter1 = new EventEmitter();
15 |
16 | const emitter1$ = fromEvent(emitter1, 'data');
17 |
18 | emitter1.emit('data', 'hello world');
19 |
20 | /*
21 | const subscription1 = emitter1$.subscribe({
22 | next: x => console.log(`Data: ${x}`)
23 | });
24 |
25 | for (let i of ['foo', 'bar', 'baz', 'quux']) {
26 | emitter1.emit('data', i);
27 | }
28 |
29 | subscription1.unsubscribe();
30 | */
31 |
32 | const emitter = new EventEmitter();
33 | const data$ = fromEvent(emitter, 'data');
34 |
35 | const error$ = fromEvent(emitter, 'error')
36 | .pipe(mergeMap(err => throwError(err)));
37 |
38 | const merged$ = merge(data$, error$);
39 |
40 | merged$.subscribe({
41 | next: x => console.log(`Data: ${x}`),
42 | error: x => console.log(`Error: ${x}`)
43 | });
44 |
45 | /*
46 | for (let i of ['foo', 'bar', 'baz', 'quux']) {
47 | emitter.emit('data', i);
48 | }
49 | emitter.emit('error', new Error('woops'));
50 | */
51 | emitter.emit('data', 'foo');
52 | emitter.emit('error', new Error('woops'));
53 | emitter.emit('data', 'bar');
54 |
55 | /*
56 | const emitter3 = new EventEmitter();
57 | const emitter3$ = fromEventPattern(
58 | h => { console.log('Added handler'); emitter3.addListener('data', h as (...args: any[]) => void); },
59 | h => { console.log('removed handler'); emitter3.removeListener('data', h as (...args: any[]) => void); }
60 | );
61 |
62 | const subscription3 = emitter3$.subscribe({
63 | next: x => console.log(`Data: ${x}`)
64 | });
65 |
66 | for (let i of ['foo', 'bar', 'baz', 'quux']) {
67 | emitter3.emit('data', i);
68 | }
69 |
70 | subscription3.unsubscribe();
71 |
72 | */
--------------------------------------------------------------------------------
/11/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AsyncSubject,
3 | BehaviorSubject,
4 | ReplaySubject,
5 | Subject,
6 | of,
7 | fromEvent
8 | } from 'rxjs';
9 |
10 | const event$ = new BehaviorSubject(12);
11 |
12 | console.log(`Current value: ${event$.getValue()}`);
13 |
14 | event$.subscribe({
15 | next: val => console.log(`Received ${val}`)
16 | });
17 |
18 | event$.next(42);
19 |
20 | console.log(`Current value: ${event$.getValue()}`);
21 |
22 | event$.next(56);
23 |
24 | console.log(`Current value: ${event$.getValue()}`);
25 |
26 | event$.next(78);
27 |
28 | console.log(`Current value: ${event$.getValue()}`);
29 |
30 |
31 |
32 | /*
33 | const event$ = new Subject();
34 |
35 | event$.next('hello');
36 |
37 | const subscription = event$.subscribe({
38 | next: val => console.log(`Received ${val}`)
39 | });
40 |
41 | event$.next('world');
42 | */
43 |
44 | /*
45 | const event$ = new ReplaySubject();
46 |
47 | event$.next('hello');
48 |
49 | const subscription = event$.subscribe({
50 | next: val => console.log(`Received ${val}`)
51 | });
52 |
53 | event$.next('world');
54 | */
55 |
56 | /*
57 | const result$ = new AsyncSubject();
58 |
59 | result$.subscribe({
60 | next: val => console.log(`First subscriber: ${val}`)
61 | });
62 |
63 | result$.next('hello world');
64 | result$.next('goodbye world');
65 | result$.complete();
66 |
67 | result$.subscribe({
68 | next: val => console.log(`Second subscriber: ${val}`)
69 | });
70 | */
71 |
72 | /*
73 | const event$ = new BehaviorSubject('initial value');
74 |
75 | console.log(`Current value: ${event$.getValue()}`);
76 |
77 | event$.subscribe({
78 | next: val => console.log(`Data: ${val}`)
79 | });
80 |
81 | event$.next('hello world');
82 |
83 | console.log(`Current value: ${event$.getValue()}`);
84 |
85 | event$.next('goodbye world');
86 |
87 | console.log(`Current value: ${event$.getValue()}`);
88 |
89 | */
90 |
--------------------------------------------------------------------------------
/08/index.ts:
--------------------------------------------------------------------------------
1 | import { of } from 'rxjs';
2 | import { filter, map } from 'rxjs/operators';
3 |
4 | function mapArr(source: Array, selector: (value: T, index: number) => R, thisArg?: any): Array {
5 | const length = source.length;
6 | const results = new Array(length);
7 | for (let i = 0; i < length; i++) {
8 | results[i] = selector.call(thisArg, source[i], i)
9 | }
10 |
11 | return results;
12 | }
13 |
14 | mapArr([1, 2, 3], x => x * x).forEach(x => console.log(`Next: ${x}`));
15 |
16 | function* mapIter(source: Iterable, selector: (value: T, index: number) => R, thisArg?: any): Iterable {
17 | let i = 0;
18 | for (let item of source) {
19 | yield selector.call(thisArg, item, i++);
20 | }
21 | }
22 |
23 | const mapped = mapIter(new Set([1, 2, 3]), x => x * x);
24 | for (let item of mapped) {
25 | console.log(`Next: ${item}`);
26 | }
27 |
28 | const obs$ = of(1, 2, 3).pipe(
29 | map(x => x * x)
30 | );
31 |
32 | obs$.subscribe({
33 | next: x => console.log(`Next: ${x}`)
34 | });
35 |
36 | // Filter
37 | function filterArr(source: Array, predicate: (value: T, index: number) => boolean, thisArg?: any) {
38 | let results = [];
39 | for (let i = 0; i < source.length; i++) {
40 | if (predicate.call(thisArg, source[i], i)) {
41 | results.push(source[i]);
42 | }
43 | }
44 |
45 | return results;
46 | }
47 |
48 | filterArr([1, 2, 3], x => x % 2 === 0).forEach(x => console.log(`Next: ${x}`));
49 |
50 | function* filterIter(source: Iterable, predicate: (value: T, index: number) => boolean, thisArg?: any) {
51 | let i = 0;
52 | for (let item of source) {
53 | if (predicate.call(thisArg, item, i++)) {
54 | yield item;
55 | }
56 | }
57 | }
58 |
59 | const filtered = filterIter(new Set([1, 2, 3]), x => x % 2 === 0);
60 | for (let item of filtered) {
61 | console.log(`Next: ${item}`);
62 | }
63 |
64 | const obs2$ = of(1, 2, 3).pipe(
65 | filter(x => x % 2 === 0),
66 | map(x => x * x)
67 | );
68 |
69 | obs2$.subscribe({
70 | next: x => console.log(`Next: ${x}`)
71 | });
--------------------------------------------------------------------------------
/15/readme.md:
--------------------------------------------------------------------------------
1 | # Day 15 - Combining Sequences of Sequences
2 |
3 | In the [previous entry](../14/readme.md), we covered combining sequences with `concat` and `merge`. Today we're going to dive a bit deeper into `merge` and `concat` with how we combine sequence of sequences.
4 |
5 | ## Smooshing a sequence of sequences
6 |
7 | In the previous section, we demonstrated `concat` which took a number of sequences and smooshed them together in order. But, what if we had a sequence of sequences, for example `Observable>` that we want to smoosh into a single sequence? We have several ways of doing that, such as we noted, whether we want concurrent or single instance at a time. In the case of `concatAll`, it only allows one sequence at a time.
8 |
9 | ```typescript
10 | import { of } from 'rxjs';
11 | import { concatAll } from 'rxjs/operators';
12 |
13 | const num1$ = of(1, 2);
14 | const num2$ = of(3, 4);
15 | const num3$ = of(5, 6);
16 |
17 | const num$ = of(num1$, num2$, num3$).pipe(concatAll());
18 |
19 | const subscription = num$.subscribe({
20 | next: item => console.log(item);
21 | });
22 | // 1
23 | // 2
24 | // 3
25 | // 4
26 | // 5
27 | // 6
28 | ```
29 |
30 | ## Concurrent smooshes with mergeAll
31 |
32 | As I've said before, when dealing with infinite sequences, chances are that `concatAll` isn't going to help us because it expects an end to the sequence. For this scenario, we have `mergeAll`, where we can give it as many inner sequences as we wish.
33 |
34 | ```typescript
35 | import { interval, of } from 'rxjs';
36 | import { map, mergeAll, take } from 'rxjs/operators';
37 |
38 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
39 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
40 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
41 | const num$ = of(num1$, num2$, num3$);
42 |
43 | const result$ = num$.pipe(mergeAll());
44 |
45 | result$.subscribe({ next: item => console.log(item) });
46 | // Third: 0
47 | // First: 0
48 | // Third: 1
49 | // Second: 0
50 | // Third: 2
51 | // First: 1
52 | // Second: 1
53 | // First: 2
54 | // Second: 2
55 | ```
56 |
57 | In addition, we can specify the limit of concurrency for the inner sequence subscriptions. For example, we could limit it to 1, which would be the same as `concatAll`.
58 |
59 | ```typescript
60 | const result$ = num$.pipe(mergeAll(1));
61 |
62 | result$.subscribe({ next: item => console.log(item) });
63 | // First: 0
64 | // First: 1
65 | // First: 2
66 | // Second: 0
67 | // Second: 1
68 | // Second: 2
69 | // Third: 0
70 | // Third: 1
71 | // Third: 2
72 | ```
73 |
74 | So, now we have a basic handle on smooshing and combining sequences together, so we'll take the next step to combine sequences via `mergeMap` and `concatMap`.
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # 2018 RxJS Advent Calendar
2 |
3 | This is the first annual RxJS Advent Calendar with 25 lessons on using [RxJS](https://github.com/reactivex/rxjs). All advent entries are written in TypeScript and can be run via in Node.js using `npx` and [ts-node](https://www.npmjs.com/package/ts-node). Follow along online at my Twitch Channel, [BluerThanBlueFalcon](https://www.twitch.tv/bluerthanbluefalcon/)!
4 |
5 | ## Daily Entries
6 |
7 | To run each entry, simply using ts-node such as the following:
8 |
9 | ```bash
10 | npx ts-node 01/index.ts
11 | ```
12 |
13 | 1. [Creating An Observable](01/readme.md)
14 | 2. [Sequences over time](02/readme.md)
15 | 3. [Creating Observables the easy way!](03/readme.md)
16 | 4. [Creating delayed and polling operations](04/readme.md)
17 | 5. [Converting to Observables](05/readme.md)
18 | 6. [Converting Events to Observables](06/readme.md)
19 | 7. [Pipe Dreams](07/readme.md)
20 | 8. [Mapping, Plucking, Tapping, and Filtering](08/readme.md)
21 | 9. [Reducing and Scanning](09/readme.md)
22 | 10. [Starting with data, ending with data, and defaulting if empty](10/readme.md)
23 | 11. [On the Subject of Subjects](11/readme.md)
24 | 12. [Implementing Redux with RxJS](12/readme.md)
25 | 13. [Combining Sequences Part 1](13/readme.md)
26 | 14. [Combining Sequences Part Deux](14/readme.md)
27 | 15. [Combining Sequences of Sequences](15/readme.md)
28 | 16. [Projecting to new sequences](16/readme.md)
29 | 17. [Getting only the latest data](17/readme.md)
30 | 18. [Getting distinct data](18/readme.md)
31 | 19. [Throttling and Debouncing Observables](19/readme.md)
32 | 20. [Skip and Take Observables](20/readme.md)
33 | 21. [Error Handling with Observables](21/readme.md)
34 | 22. [Publishing and Sharing](22/readme.md)
35 | 23. [Buffers and Windows](23/readme.md)
36 | 24. [Parallelizing Work with ForkJoin](24/readme.md)
37 | 25. [Grouping work with groupBy](25/readme.md)
38 |
39 | ## License
40 |
41 | MIT License
42 |
43 | Copyright (c) 2018 Matthew Podwysocki
44 |
45 | Permission is hereby granted, free of charge, to any person obtaining a copy
46 | of this software and associated documentation files (the "Software"), to deal
47 | in the Software without restriction, including without limitation the rights
48 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
49 | copies of the Software, and to permit persons to whom the Software is
50 | furnished to do so, subject to the following conditions:
51 |
52 | The above copyright notice and this permission notice shall be included in all
53 | copies or substantial portions of the Software.
54 |
55 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
56 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
57 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
58 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
59 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
60 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
61 | SOFTWARE.
62 |
--------------------------------------------------------------------------------
/21/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Observable,
3 | of,
4 | range,
5 | throwError,
6 | timer,
7 | zip
8 | } from 'rxjs';
9 |
10 | import {
11 | catchError,
12 | delayWhen,
13 | finalize,
14 | map,
15 | mergeMap,
16 | onErrorResumeNext,
17 | retry,
18 | retryWhen,
19 | tap
20 | } from 'rxjs/operators';
21 |
22 | let retryCount = 0;
23 | const obs$ = new Observable(observer => {
24 | if (++retryCount == 3) {
25 | observer.next(42);
26 | observer.complete();
27 | } else {
28 | console.log('failed call');
29 | observer.error(new Error('woops'));
30 | }
31 | });
32 |
33 | const num$ = obs$.pipe(
34 | retryWhen(errors => {
35 | return zip(errors, range(1, 3)).pipe(
36 | map(([_, index]) => index),
37 | tap(item => console.log(`Retrying after ${item} seconds`)),
38 | delayWhen(item => timer(item * 1000))
39 | );
40 | })
41 | );
42 |
43 | num$.subscribe({
44 | next: item => console.log(`Next: ${item}`),
45 | error: err => console.log(`Error: ${err.message}`)
46 | });
47 |
48 | /*
49 | const num$ = of(1, 2, 3).pipe(
50 | map(item => {
51 | if (item > 2) {
52 | throw new Error('woops');
53 | } else {
54 | return item;
55 | }
56 | })
57 | );
58 | */
59 |
60 | /*
61 | const num$ = throwError(new Error('woops'));
62 | */
63 |
64 | /*
65 | const num$ = of(1, 2, 3).pipe(
66 | mergeMap(item => {
67 | if (item > 2) {
68 | return throwError(new Error('woops'));
69 | }
70 |
71 | return of(item * item);
72 | }),
73 | catchError(err => {
74 | console.log(`Error caught: ${err.message}`);
75 | return of(42);
76 | })
77 | );
78 | */
79 |
80 | /*
81 | const num1$ = throwError(new Error('first'));
82 | const num2$ = throwError(new Error('second'));
83 | const num3$ = of(42);
84 | const num4$ = of(56);
85 |
86 | const num$ = num1$.pipe(
87 | onErrorResumeNext(num2$, num3$, num4$)
88 | );
89 |
90 | num$.subscribe({
91 | next: item => console.log(`Next: ${item}`),
92 | error: err => console.log(`Error: ${err.message}`)
93 | });
94 | */
95 |
96 | /*
97 | of(42).pipe(
98 | finalize(() => console.log('Finally called'))
99 | ).subscribe({
100 | next: item => console.log(`Next: ${item}`),
101 | error: err => console.log(`Error: ${err.message}`),
102 | complete: () => console.log('Done')
103 | });
104 |
105 | throwError(new Error('woops')).pipe(
106 | finalize(() => console.log('Finally called'))
107 | ).subscribe({
108 | next: item => console.log(`Next: ${item}`),
109 | error: err => console.log(`Error: ${err.message}`),
110 | complete: () => console.log('Done')
111 | });
112 | */
113 | /*
114 | let retryCount = 0;
115 | const obs$ = new Observable(observer => {
116 | if (++retryCount == 3) {
117 | observer.next(42);
118 | observer.complete();
119 | } else {
120 | observer.error(new Error('woops'));
121 | }
122 | });
123 |
124 | /*
125 | const num$ = obs$.pipe(retry(3));
126 | */
127 |
128 | /*
129 | const num$ = obs$.pipe(
130 | retryWhen(errors => {
131 | return zip(errors, range(1, 3)).pipe(
132 | map(([_, index]) => index),
133 | tap(item => console.log(`Retrying after ${item} seconds`)),
134 | delayWhen(item => timer(item * 1000))
135 | );
136 | })
137 | );
138 |
139 |
140 | num$.subscribe({
141 | next: item => console.log(`Next: ${item}`),
142 | error: err => console.log(`Error: ${err.message}`)
143 | });
144 |
145 | */
--------------------------------------------------------------------------------
/14/readme.md:
--------------------------------------------------------------------------------
1 | # Day 14 - Combining Sequences Part Deux
2 |
3 | In the [previous entry](../13/readme.md), we covered combining Observable sequences together using `zip`, `combineLatest` and `withLatestFrom`. Today, we'll be looking at two other ways of combining sequences, with `concat` and `merge`.
4 |
5 | ## Combining sequences with concat
6 |
7 | The first operator we will look at today is `concat`, which is to combine the sequences, by waiting until the end before starting the next sequence. This keeps all values in order from the sequences no matter when they were emitted. Let's walk through an example of `concat`.
8 |
9 | ```typescript
10 | import { concat, of } from 'rxjs';
11 |
12 | const num1$ = of(1, 2);
13 | const num2$ = of(3, 4);
14 | const num3$ = of(5, 6);
15 |
16 | const num$ = concat(num1$, num2$, num3$);
17 | const subscription = num$.subscribe({
18 | next: item => console.log(item)
19 | });
20 | // 1
21 | // 2
22 | // 3
23 | // 4
24 | // 5
25 | // 6
26 | ```
27 |
28 | As you will notice, the next sequence is triggered by the previous sequence finishing, thus putting the sequences in order. But, we're dealing with collections over time, so when we're dealing with events such as move movements, that's not super useful, so let's do a concurrent combining of sequences.
29 |
30 | ## Combining sequences with merge
31 |
32 | In order to do concurrent combining of sequences into a single sequence, we have the `merge` operator. This allows us to merge any number of Observables, but also control the concurrency via adding a Scheduler, or a maximum number of concurrent sequences.
33 |
34 | ```typescript
35 | merge(...args: Observable[]): Observable
36 | merge(...args: Observable[], maxConcurrent: number): Observable;
37 | merge(...args: Observable[], scheduler: SchedulerLike): Observable;
38 | ```
39 |
40 | To make this more concrete, let's take the `interval` calls from the previous lesson, and merge them together. Note how the sequences are combined with the third sequence firing first, followed by the first, and then the second.
41 |
42 | ```typescript
43 | import { interval, merge } from 'rxjs';
44 | import { map, take } from 'rxjs/operators';
45 |
46 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
47 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
48 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
49 |
50 | const num$ = merge(num1$, num2$, num3$);
51 | const subscription = num$.subscribe({
52 | next: item => console.log(item)
53 | });
54 | // Third: 0
55 | // First: 0
56 | // Third: 1
57 | // Second: 0
58 | // Third: 2
59 | // First: 1
60 | // Second: 1
61 | // First: 2
62 | // Second: 2
63 | ```
64 |
65 | We could control the amount of concurrency with `merge` via the last parameter. In fact, we could turn `merge` into `concat` by setting the max concurrency at 1.
66 |
67 | ```typescript
68 | import { interval, merge } from 'rxjs';
69 | import { map, take } from 'rxjs/operators';
70 |
71 | const num1$ = interval(1000).pipe(map(x => `First: ${x}`), take(3));
72 | const num2$ = interval(1500).pipe(map(x => `Second: ${x}`), take(3));
73 | const num3$ = interval(500).pipe(map(x => `Third: ${x}`), take(3));
74 |
75 | const num$ = merge(num1$, num2$, num3$, 1);
76 | const subscription = num$.subscribe({
77 | next: item => console.log(item)
78 | });
79 | // First: 0
80 | // First: 1
81 | // First: 2
82 | // Second: 0
83 | // Second: 1
84 | // Second: 2
85 | // Third: 0
86 | // Third: 1
87 | // Third: 2
88 | ```
89 |
90 | Despite the sequences themselves being asynchronous, we didn't subscribe to the next one until the first sequence was finished. Now that we have some of the basics down of combining sequences, we will get into the whole `mergeAll`, `concatAll`, and of course all the *Map operators like `concatMap`, `mergeMap`, `switchMap` and others. Stay tuned!
--------------------------------------------------------------------------------
/17/readme.md:
--------------------------------------------------------------------------------
1 | # Day 17 - Getting only the latest data
2 |
3 | In the [previous entry](../16/readme.md), we covered both `concatMap` and `mergeMap`, how we compose together operations such as querying Wikipedia with queries coming from events. This is the start of an autosuggest scenario where we want to give the user some suggestions when they are typing, giving them an idea of what results they can expect.
4 |
5 | Making sure the user has the right data is key in the autosuggest scenario. As we've discovered, `mergeMap` allows us to query an external source, but unlike `concatMap`, the order of the results isn't guaranteed. For example, I could query for "react", and then "reactive", there is no guarantee that "reactive" comes back last, thus confusing the user. To get around this issue, what if we could cancel the previous request and ensure that we do indeed have the latest value? That's where the `switchMap` operator comes into play.
6 |
7 | Let's review of where we were with our autocomplete scenario from before with using `mergeMap`, searching Wikipedia with our terms from our `EventEmitter` and its `data` event.
8 |
9 | ```typescript
10 | import { fromEvent } from 'rxjs';
11 | import { mergeMap } from 'rxjs/operators';
12 | import axios from 'axios';
13 | import { EventEmitter } from 'events';
14 |
15 | async function searchWikipedia(term: string): Promise> {
16 | const response = await axios.get('http://en.wikipedia.org/w/api.php', {
17 | params: {
18 | action: 'opensearch',
19 | format: 'json',
20 | search: term
21 | }
22 | });
23 | return response.data[1];
24 | }
25 |
26 | const eventEmitter = new EventEmitter();
27 | const value$ = fromEvent(eventEmitter, 'data');
28 |
29 | const result$ = value$.pipe(
30 | mergeMap(searchWikipedia)
31 | );
32 |
33 | result$.subscribe({
34 | next: item => console.log(`First match ${item[0]}`)
35 | });
36 |
37 | eventEmitter.emit('data', 'reacti');
38 | eventEmitter.emit('data', 'reactive');
39 | eventEmitter.emit('data', 'react');
40 | // First match Reactive oxygen species
41 | // First match Reactive
42 | // First match React
43 | ```
44 |
45 | As you may notice, we have our results out of order. In an autosuggest scenario, the last thing you'd want to do is update the user with old data, so we need to cancel the previous request. We can do that with the `switchMap`, which unsubscribes from the previous Observable and tears down the logic, for example, cancelling a `fetch` API call.
46 |
47 | ```typescript
48 | import { fromEvent } from 'rxjs';
49 | import { switchMap } from 'rxjs/operators';
50 | import axios from 'axios';
51 | import { EventEmitter } from 'events';
52 |
53 | async function searchWikipedia(term: string): Promise> {
54 | const response = await axios.get('http://en.wikipedia.org/w/api.php', {
55 | params: {
56 | action: 'opensearch',
57 | format: 'json',
58 | search: term
59 | }
60 | });
61 | return response.data[1];
62 | }
63 |
64 | const eventEmitter = new EventEmitter();
65 | const value$ = fromEvent(eventEmitter, 'data');
66 |
67 | const result$ = value$.pipe(
68 | switchMap(searchWikipedia)
69 | );
70 |
71 | result$.subscribe({
72 | next: item => console.log(`First match ${item[0]}`)
73 | });
74 |
75 | eventEmitter.emit('data', 'reacti');
76 | eventEmitter.emit('data', 'reactive');
77 | eventEmitter.emit('data', 'react');
78 | // First match React
79 | ```
80 |
81 | Now, what you'll notice is that we're not getting three responses back, and instead only one, that with our query of `react` returned `React`, which is exaclty what we need. We can further enhance this scenario with the events to make them less noisy such as debouncing the user input, and ensuring we're only querying when the data has in fact changed. We'll talk about that in the next post! Stay tuned!
--------------------------------------------------------------------------------
/16/readme.md:
--------------------------------------------------------------------------------
1 | # Day 16 - Projecting to new sequences
2 |
3 | In the [previous entry](../15/readme.md), we covered how we can smoosh a sequence of sequences with both `concatAll` and `mergeAll`. In this entry, we'll explore some of the most common operators you'll encounter with projecting a value to a new sequence via `mergeMap`, and `concatMap`. These operators, as you might think, are simply a combination of the `map` operator, which produces the Observable of Observables, and then it's up to us how we combine those sequences into a single sequence, whether concurrent or not.
4 |
5 | The reason why these operators are important is because we need ways to compose a value from our current Observable, for example a stream of text values as you type, and then you want to take that value and query a service which is also an Observable or Promise that we'd want to flatten into a single answer. These operators are all aptly named *Map operators, where the style of combining is the first part of the name whether it is `mergeMap`, `concatMap`, or the ever important `switchMap` which we will get into in the next post.
6 |
7 | ## Projecting a new value with concatMap
8 |
9 | The first one we will look at is `concatMap`, which is a combination of the previous operators we've covered, `map`, and `concatAll`. In this first example, we'll take a value from our list, and delay the value by a second times its value by using the `delay` operator and then mapping it so we have a nice value to show how long each was delayed by. We're diving into the deeper end of composition, but I want to give the idea of the art of the possible.
10 |
11 | ```typescript
12 | import { of } from 'rxjs';
13 | import { concatMap, delay, map } from 'rxjs/operators';
14 |
15 | const num$ = of(1, 2, 3).pipe(
16 | concatMap(item => of(item).pipe(
17 | delay(item * 1000),
18 | map(item => `Item ${item} delayed by ${item * 1000} ms`)
19 | ))
20 | );
21 |
22 | const subscription = num$.subscribe({
23 | next: item => console.log(item)
24 | });
25 | // Item 1 delayed by 1000ms
26 | // Item 2 delayed by 2000ms
27 | // Item 3 delayed by 3000ms
28 | ```
29 |
30 | ## Projecting a new value with mergeMap
31 |
32 | Now that we have a basic understanding of `concatMap`, let's talk about a more concurrent approach where we can take the data as it comes in and project it to the new sequence. For example, we could query Wikipedia with Axios using Node.js with events from an `EventEmitter`. Remember, a `Promise` that is returned from this will be converted automatically into an Observable.
33 |
34 | ```typescript
35 | import { fromEvent } from 'rxjs';
36 | import { mergeMap } from 'rxjs/operators';
37 | import axios from 'axios';
38 | import { EventEmitter } from 'events';
39 |
40 | async function searchWikipedia(term: string): Promise> {
41 | const response = await axios.get('http://en.wikipedia.org/w/api.php', {
42 | params: {
43 | action: 'opensearch',
44 | format: 'json',
45 | search: term
46 | }
47 | });
48 | return response.data[1];
49 | }
50 |
51 | const eventEmitter = new EventEmitter();
52 | const value$ = fromEvent(eventEmitter, 'data');
53 |
54 | const result$ = value$.pipe(
55 | mergeMap(searchWikipedia)
56 | );
57 |
58 | result$.subscribe({
59 | next: item => console.log(`First match ${item[0]}`)
60 | });
61 |
62 | eventEmitter.emit('data', 'react');
63 | eventEmitter.emit('data', 'reacti');
64 | eventEmitter.emit('data', 'reactive');
65 | // First match Reactive
66 | // First match React
67 | // First match Reactive oxygen species
68 | ```
69 |
70 | We can then emit three queries at once and see the results come in. Note of course, since this is a merge operation, the results can come in any order, unlike with `concatMap`. So, the question is, how would we fix it so that we only get the latest and greatest query and cancel the rest? That's for the next lesson! Stay tuned!
--------------------------------------------------------------------------------
/02/readme.md:
--------------------------------------------------------------------------------
1 | # Day 2 - Sequences over time
2 |
3 | In the [previous day](../01/readme.md), we looked at the basics of creating Observables and the parts required such as Observers and Subscriptions. There is another aspect that is missing from the initial equation which is that Observables are push based sequences over time, noting that last part, time. RxJS was designed with the notion of virtual time and a virtual clock which means you can say when a particular action happens. This concept is introduced with the idea of Schedulers, which not only controls when a particular action happens, but also the context of an action as well.
4 |
5 | Let's make this a little bit more concrete. Let's say that we want to use `requestAnimationFrame` to schedule our next actions. We could usually do something like the following where we schedule an action using `requestAnimationFrame`, and as part of the teardown, we have `cancelAnimationFrame` to clean up any resources or if we want to immediately cancel the item.
6 |
7 | ```typescript
8 | import { Observable } from 'rxjs';
9 |
10 | const number$ = new Observable(observer => {
11 | let id = requestAnimationFrame(() => {
12 | observer.next(42);
13 | observer.complete();
14 | });
15 |
16 | return () => cancelAnimationFrame(id);
17 | });
18 | ```
19 |
20 | This, of course, is a nice to have, but it's not super testable in a way we could use an abstraction like a Scheduler which would allow us to swap out `requestAnimationFrame` for a virtual time stamp for testing or historical data processing. Instead, let's look at how we could do it better.
21 |
22 | In RxJS, we have two interfaces that are required for any emitting of data, a `Scheduler`, and a `SchedulerAction`. The scheduler schedules the unit of work called the `SchedulerAction` and returns a `Subscription` which like for our Observable, allows us to tear down the work at any point.
23 |
24 | ```typescript
25 | export interface SchedulerLike {
26 | now(): number;
27 | schedule(work: (this: SchedulerAction, state?: T) => void, delay?: number, state?: T): Subscription;
28 | }
29 |
30 | export interface SchedulerAction extends Subscription {
31 | schedule(state?: T, delay?: number): Subscription;
32 | }
33 | ```
34 |
35 | Since our work is rather easy, we can skip a lot of the overhead and implement our new item like the following just using a function and not caring about any unit of work `SchedulerAction`. RxJS has a number of built-in schedulers so we don't need to implement our own for most work we need.
36 |
37 | The following schedulers are available to you from RxJS:
38 | - `animationFrameScheduler` - Schedule an async action using `requestAnimationFrame`
39 | - `asapScheduler` - Schedule an async action as soon as possible such as using `setImmediate`
40 | - `asyncScheduler` - Schedule an async action using `setInterval`/`clearInterval`
41 | - `VirtualTimeScheduler` - define your own time semantics in virtual time
42 |
43 | ```typescript
44 | import {
45 | Observable,
46 | SchedulerLike,
47 | animationFrameScheduler
48 | } from 'rxjs';
49 |
50 | // Polyfill requestAnimationFrame
51 | import * as raf from 'raf';
52 | raf.polyfill();
53 |
54 | function once(value: T, scheduler: SchedulerLike) {
55 | return new Observable(observer => {
56 | return scheduler.schedule(() => {
57 | observer.next(value);
58 | observer.complete();
59 | });
60 | });
61 | }
62 |
63 | const subscription = once(42, animationFrameScheduler)
64 | .subscribe({
65 | next:x => console.log(`Next: ${x}`),
66 | complete: () => console.log(`Complete!`)
67 | });
68 | ```
69 |
70 | Running this, we get the following:
71 | ```bash
72 | $ npx ts-node 02/index.ts
73 | Next: 42
74 | Complete!
75 | ```
76 |
77 | Since this action is async by nature, we can preempt this call by immediately calling `subscription.unsubscribe()` if you so desire. Join us next time as we get into easier ways of creating observables rather than by hand!
78 |
--------------------------------------------------------------------------------
/22/index.ts:
--------------------------------------------------------------------------------
1 | /*
2 | import { Observable } from 'rxjs';
3 |
4 | const cold$ = new Observable(observer => {
5 | for (let item of [42, 56]) {
6 | console.log(`Observable next: ${item}`);
7 | observer.next(item);
8 | }
9 | });
10 |
11 | const subcription1 = cold$.subscribe({
12 | next: item => console.log(`Subscription 1: ${item}`)
13 | });
14 |
15 | const subcription2 = cold$.subscribe({
16 | next: item => console.log(`Subscription 2: ${item}`)
17 | });
18 | */
19 |
20 | /*
21 | import { Observable, ConnectableObservable } from 'rxjs';
22 | import { publish } from 'rxjs/operators';
23 |
24 | const cold$ = new Observable(observer => {
25 | for (let item of [42, 56]) {
26 | console.log(`Observable next: ${item}`);
27 | observer.next(item);
28 | }
29 | observer.complete();
30 | });
31 |
32 | const hot$ = (cold$.pipe(publish()) as ConnectableObservable).refCount();
33 |
34 | const subcription1 = hot$.subscribe({
35 | next: item => console.log(`Subscription 1: ${item}`)
36 | });
37 |
38 | const subcription2 = hot$.subscribe({
39 | next: item => console.log(`Subscription 2: ${item}`)
40 | });
41 | */
42 |
43 | /*
44 | import { interval, merge, range, zip } from 'rxjs';
45 | import { map, publish } from 'rxjs/operators';
46 |
47 | const hot$ =
48 | zip(
49 | interval(1000),
50 | range(0, 2)
51 | )
52 | .pipe(publish(multicasted$ => {
53 | return merge(
54 | multicasted$.pipe(map(x => `stream1: ${x}`)),
55 | multicasted$.pipe(map(x => `stream2: ${x}`)),
56 | multicasted$.pipe(map(x => `stream3: ${x}`))
57 | );
58 | }));
59 |
60 | const subcription1 = hot$.subscribe({
61 | next: item => console.log(`Subscription 1: ${item}`)
62 | });
63 |
64 | const subcription2 = hot$.subscribe({
65 | next: item => console.log(`Subscription 2: ${item}`)
66 | });
67 | */
68 |
69 | /*
70 | import { Observable, ConnectableObservable } from 'rxjs';
71 | import { publishBehavior } from 'rxjs/operators';
72 |
73 | const cold$ = new Observable(observer => {
74 | for (let item of [42, 56]) {
75 | console.log(`Observable next: ${item}`);
76 | observer.next(item);
77 | }
78 | });
79 |
80 | const hot$ = cold$.pipe(publishBehavior(-1)) as ConnectableObservable;
81 |
82 | const subcription1 = hot$.subscribe({
83 | next: item => console.log(`Subscription 1: ${item}`)
84 | });
85 |
86 | const subcription2 = hot$.subscribe({
87 | next: item => console.log(`Subscription 2: ${item}`)
88 | });
89 |
90 | hot$.connect();
91 |
92 | const subcription3 = hot$.subscribe({
93 | next: item => console.log(`Subscription 3: ${item}`)
94 | });
95 | */
96 |
97 | /*
98 | import { Observable, ConnectableObservable } from 'rxjs';
99 | import { publishReplay } from 'rxjs/operators';
100 |
101 | const cold$ = new Observable(observer => {
102 | for (let item of [42, 56]) {
103 | console.log(`Observable next: ${item}`);
104 | observer.next(item);
105 | }
106 | });
107 |
108 | const hot$ = cold$.pipe(publishReplay(2)) as ConnectableObservable;
109 |
110 | const subcription1 = hot$.subscribe({
111 | next: item => console.log(`Subscription 1: ${item}`)
112 | });
113 |
114 | const subcription2 = hot$.subscribe({
115 | next: item => console.log(`Subscription 2: ${item}`)
116 | });
117 |
118 | hot$.connect();
119 |
120 | const subcription3 = hot$.subscribe({
121 | next: item => console.log(`Subscription 3: ${item}`)
122 | });
123 | */
124 |
125 | import { Observable, ConnectableObservable } from 'rxjs';
126 | import { publishLast } from 'rxjs/operators';
127 |
128 | const cold$ = new Observable(observer => {
129 | for (let item of [42, 56]) {
130 | console.log(`Observable next: ${item}`);
131 | observer.next(item);
132 | }
133 |
134 | observer.complete();
135 | });
136 |
137 | const hot$ = cold$.pipe(publishLast()) as ConnectableObservable;
138 |
139 | const subcription1 = hot$.subscribe({
140 | next: item => console.log(`Subscription 1: ${item}`)
141 | });
142 |
143 | const subcription2 = hot$.subscribe({
144 | next: item => console.log(`Subscription 2: ${item}`)
145 | });
146 |
147 | hot$.connect();
148 |
149 | const subcription3 = hot$.subscribe({
150 | next: item => console.log(`Subscription 3: ${item}`)
151 | });
--------------------------------------------------------------------------------
/13/readme.md:
--------------------------------------------------------------------------------
1 | # Day 13 - Combining Sequences Part 1
2 |
3 | In the [previous entry](../12/readme.md), we covered Redux and the Flux architecture, and how you could implement it using nothing but RxJS! We will at some point revisit this with, especially with async actions, but for now, let's move on to a new subject, combining sequences. In this post, we will cover how we could combine sequences using operators like `zip`, and `combineLatest`.
4 |
5 | ## Zipping together sequences with zip
6 |
7 | The first operator we will look at is `zip`. This operator takes any number of sequences, and zips them together into a new Observable containing the elements from both Observables at the same position. We could write this easily for Iterables such as the following code where it emits values only when both sides have values, and if one side is shorter than the other, the longer side is truncated.
8 |
9 | ```typescript
10 | function* zip(source1: Iterable, source2: Iterable): Iterable> {
11 | let it1 = source[Symbol.iterator](), it2 = source[Symbol.iterator]();
12 | while (true) {
13 | let next1 = it1.next(), next2 = it2.next();
14 | if (next1.done || next2.done) {
15 | break;
16 | }
17 |
18 | yield [next1.value, next2.value];
19 | }
20 | }
21 |
22 | const results = zip([1, 2, 3], [4, 5, 6]);
23 | for (let item of results) {
24 | console.log(item);
25 | }
26 | // [1, 4]
27 | // [2, 5]
28 | // [3, 6]
29 | ```
30 |
31 | With Observables, we get the same semantics but with push collections instead of pull.
32 |
33 | ```typescript
34 | import { of, zip } from 'rxjs';
35 |
36 | const num1$ = of(1, 2, 3);
37 | const num2$ = of(4, 5, 6);
38 |
39 | const result$ = zip(num1$, num2$);
40 | const subscription = result$.subscribe({
41 | next: item => console.log(item)
42 | });
43 | // [1, 4]
44 | // [2, 5]
45 | // [3, 6]
46 | ```
47 |
48 | ## Zipping together with combineLatest
49 |
50 | But dealing with concurrent collections, it's not super reasonable to ensure that both collections emit values at the same time at the same index. Instead, we have `combineLatest` which emits when both sides have a value, and as the value on any of the sequences change. Let's walk through an example using Observables using `interval` at different times, taking 3 of each.
51 |
52 | ```typescript
53 | import { combineLatest, interval } from 'rxjs';
54 | import { map, take } from 'rxjs/operators';
55 |
56 | const num1$ = interval(1000).pipe(map(item => `First ${item}`), take(3));
57 | const num2$ = interval(1500).pipe(map(item => `Second ${item}`), take(3));
58 | const num3$ = interval(500).pipe(map(item => `Third ${item}`), take(3));
59 |
60 | const num$ = combineLatest(num1$, num2$, num3$);
61 | num$.subscribe({ next: item => console.log(item) });
62 | // [First: 0, Second: 0, Third: 1]
63 | // [First: 0, Second: 0, Third: 2]
64 | // [First: 1, Second: 0, Third: 2]
65 | // [First: 1, Second: 1, Third: 2]
66 | // [First: 2, Second: 1, Third: 2]
67 | // [First: 2, Second: 2, Third: 2]
68 | ```
69 |
70 | What's interesting about this approach is that by the time the first two Observables are ready to omit, the third Observable has already yielded its second value.
71 |
72 | ## Zipping with withLatestFrom
73 |
74 | The third way of combining Observables is `withLatestFrom`, which combines the source Observable with other Observable to create an Observable whose values are calculated from the latest values of each, only when the source emits. Let's look at how this differs from our `combineLatest` from above.
75 |
76 | ```typescript
77 | import { interval } from 'rxjs';
78 | import { map, take, withLatestFrom } from 'rxjs/operators';
79 |
80 | const num1$ = interval(1000).pipe(map(item => `First ${item}`), take(3));
81 | const num2$ = interval(1500).pipe(map(item => `Second ${item}`), take(3));
82 | const num3$ = interval(500).pipe(map(item => `Third ${item}`), take(3));
83 |
84 | const num$ = num1$.pipe(withLatestFrom(num2$, num3$));
85 | num$.subscribe({ next: item => console.log(item) });
86 | // First: 1, Second: 0, Third: 2
87 | // First: 2, Second: 1, Third: 2
88 | ```
89 |
90 | To explain the output here, by the time the second Observable emits its first, the source itself has moved on to second item, and the third Observable has already finished. That's enough for today, but check back tomorrow when we work on `merge` versus `concat` and combining sequences.
--------------------------------------------------------------------------------
/01/readme.md:
--------------------------------------------------------------------------------
1 | # Day 1 - Creating an Observable
2 |
3 | The first entry for our calendar is creating an Observable.
4 |
5 | ## What is this RxJS stuff?
6 |
7 | What is an Observable? Simply put, it is a push-based event emitter which can push 0 to infinite amounts of data.
8 |
9 | Next, we will create an Observer, which is a sink that an Observable push data into when you want to listen for the data in the cases of more data via `next`, and error occurred via `error`, or the stream has completed via `complete`.
10 |
11 | Finally, we can hook it all together with subscribing the observer to the observable. This subscription process by calling `subscribe` on the Observable, passing in our Observer. This returns a Subscription which contains the teardown logic required to clean up any resources created by the Observable stream.
12 |
13 | ## Walking through the code
14 |
15 | Let's walk through the [first example](index.ts), creating an Observable. First, we need to import RxJS, only getting the Observable that is required.
16 |
17 | ```typescript
18 | import { Observable } from 'rxjs';
19 | ```
20 |
21 | Creating an Observable gives you a sense of the Observable lifecycle. When you create a new Observable, you pass in an Observer which you can emit data to at any point, 0 to infinite number of times `next` can be called, followed by an optional `error` or `complete` call. Finally, when we create our Observable, we also include any teardown logic such as clearing a timeout or interval, or cancelling an incomplete network request.
22 |
23 | ```typescript
24 | const source$ = new Observable(observer => {
25 | observer.next(42);
26 | observer.complete();
27 |
28 | return function teardown () { console.log('Teardown'); };
29 | });
30 | ```
31 |
32 | Let's go more advanced with a timer based approach using `setInterval` to emit values every second, and then the teardown will cancel via `clearInterval`. This example will run 5 times and then complete the stream. We return a teardown function which calls `clearInterval` on our given `id` so that we can stop the intervals.
33 |
34 | ```typescript
35 | let n = 0;
36 | let id = 0;
37 |
38 | // Create an Observable from scratch
39 | const number$ = new Observable(observer => {
40 | id = setInterval(() => {
41 | // Counter to run 5 times
42 | if (n++ < 5) {
43 | observer.next(n);
44 | } else {
45 | // When finished, clear the interval and complete the stream
46 | observer.complete();
47 | }
48 | }, 1000);
49 |
50 | return () => clearInterval(id);
51 | });
52 | ```
53 |
54 | Next, we can create an observer or data sink to emit data values into at any point. Remember from above, we have three choices, 0 to infinite data via `next`, and handling errors via `error` and handling completion via `complete`. In this case, we are not worried about errors, so we will only implement `next` and `complete`.
55 |
56 | ```typescript
57 | // Handle only the next and completion handlers
58 | const observer = {
59 | next: (x: number) => {
60 | console.log(`Next: ${x}`);
61 | },
62 | complete: () => {
63 | console.log(`Complete!`);
64 | }
65 | };
66 | ```
67 |
68 | Finally, we can tie it all together, calling `subscribe` on the Observable with our Observer instance. This will return our subscription that we can unsubscribe from at any time. Note that since the inner observer calls `complete`, the teardown logic will happen automatically and the subscription automatically unsubscribes.
69 |
70 | ```typescript
71 | // Subscribe to the Observable with our observer
72 | const subscription = number$.subscribe(observer);
73 | ```
74 |
75 | This then emits the following data:
76 | ```bash
77 | $ npx ts-node 01/index.ts
78 | Next: 1
79 | Next: 2
80 | Next: 3
81 | Next: 4
82 | Next: 5
83 | Complete!
84 | ```
85 |
86 | We can terminate this sequence early, however, since it will take 5 seconds to complete, we could cancel after 2.5 seconds.
87 |
88 | ```typescript
89 | // Subscribe to the Observable with our observer
90 | const subscription = number$.subscribe(observer);
91 |
92 | // Terminate the sequence after 2.5 seconds
93 | setTimeout(() => subscription.unsubscribe(), 2500);
94 | ```
95 |
96 | This then changes the output to the following:
97 | ```bash
98 | $ npx ts-node 01/index.ts
99 | Next: 1
100 | Next: 2
101 | ```
102 |
103 | I hope you learned the basics as we start our journey through the advent of RxJS!
104 |
--------------------------------------------------------------------------------
/12/readme.md:
--------------------------------------------------------------------------------
1 | # Day 12 - Implementing Redux with RxJS
2 |
3 | In the [previous entry](../11/readme.md), we covered subjects, and their uses, whether it is `Subject`, `ReplaySubject`, `AsyncSubject`, and lastly the `BehaviorSubject`. This all leads up to understanding the [Redux](https://redux.js.org/) library and understanding state management and its patterns. From there, we will understand how we could implement those features from Redux using RxJS and the knowledge we've covered so far in the series.
4 |
5 | ## Intro to Redux
6 |
7 | At its heart, Redux is a state container for JavaScript apps. It became particularly popular in the React ecosystem. The library was based on the Flux architecture which introduced the concepts of stores, actions and dispatchers. Redux diverged from Flux a little bit renaming stores to reducers, no concept of a dispatcher, and relies on pure functions.
8 |
9 | Let's implement a simple counter using the concepts of Redux, with our "store" and our reducer, and how we can send messages to it. Let's add some boilerplate so that we get nicely typed experience instead of magic strings, using enums.
10 |
11 | ```typescript
12 | import { createStore } from 'redux';
13 |
14 | enum ActionType {
15 | Increment,
16 | Decrement
17 | }
18 |
19 | interface ReduxAction {
20 | type: ActionType;
21 | }
22 |
23 | interface ReduxState {
24 | value: number;
25 | }
26 | const INITIAL_STATE = { value: 0 };
27 | ```
28 |
29 | Now, let's get to the heart of the matter, which is to implement the reducer function. This will react to actions as they come in, such as `Increment` or `Decrement`, and returning new state based upon the action and previous state.
30 |
31 | ```typescript
32 | function counter(state: ReduxState = INITIAL_STATE, action: ReduxAction) {
33 | switch (action.type) {
34 | case ActionType.Increment:
35 | return Object.freeze({ value: state.value + 1 });
36 | case ActionType.Decrement:
37 | return Object.freeze({ value: state.value - 1 });
38 | default:
39 | return state;
40 | }
41 | }
42 | ```
43 |
44 | We can then create the store which is bound to our reducer function. This store allows us to get the current state, dispatch an action to the reducer, and subscribe to any state changes.
45 |
46 | ```typescript
47 | const store = createStore(counter);
48 | ```
49 |
50 | We will keep track of all state changes in our `render` function, where we subscribe to changes via, surprise, the `subscribe` method. This method takes no arguments and returns no value. We can then initially render the data.
51 |
52 | ```typescript
53 | function render() {
54 | console.log(`Rendering changes with ${store.getState().value}`);
55 | }
56 |
57 | render();
58 | const unsubscribe = store.subscribe(render);
59 | ```
60 |
61 | Now we can increment or decrement the state via dispatching an action via the `dispatch` method. Doing so call our reducer, and then trigger our `render` function.
62 |
63 | ```typescript
64 | // Increment
65 | store.dispatch({ type: ActionType.Increment });
66 | // Rendering changes with 1
67 |
68 | // Decrement
69 | store.dispatch({ type: ActionType.Decrement });
70 | // Rendering changes with 0
71 | ```
72 |
73 | You can see some similarities here to what we've already learned so far. In fact, there are some learnings that Redux got from RxJS in the [Prior Art](https://redux.js.org/introduction/prior-art#rxjs) section of the Redux site. For a full source for this example, see [`redux.ts`](redux.ts)
74 |
75 | ## Implementing Redux in RxJS
76 |
77 | Using Subjects, `startWith` and `scan`, we can implement the core concepts of Redux using RxJS directly. In fact, we could have easily implemented the main part of Redux as shown above as the following code.
78 |
79 | ```typescript
80 | import { Subject } from 'rxjs';
81 | import { scan, startWith } from 'rxjs/operators';
82 |
83 | const store$ = new Subject();
84 | const subscription = store$
85 | .pipe(
86 | startWith(initialState)
87 | scan(reducer),
88 | )
89 | .subscribe({
90 | next: render
91 | });
92 | ```
93 |
94 | So, let's look at the changes you need to make to fit it into an RxJS solution. We would need to change the `render` function since we do not have access to the current value, instead we get the new state passed into it. The other change is using `next` to dispatch our actions to our reducer instead of the `dispatch` method.
95 |
96 | ```typescript
97 | const store$ = new Subject();
98 | const action$ = store$.pipe(
99 | scan(counter, INITIAL_STATE),
100 | startWith(INITIAL_STATE)
101 | );
102 |
103 | const unsubscribe = action$.subscribe({ next: render });
104 |
105 | function render(state: ReduxState = INITIAL_STATE) {
106 | console.log(`Rendering changes with ${state.value}`);
107 | }
108 |
109 | // Increment
110 | store$.next({ type: ActionType.Increment });
111 | // Rendering changes with 1
112 |
113 | // Decrement
114 | store$.next({ type: ActionType.Decrement });
115 | // Rendering changes with 0
116 | ```
117 |
118 | That's it for the basic implementation of Redux using RxJS, but we will dive further into Redux and async actions soon enough! Stay tuned!
119 |
--------------------------------------------------------------------------------
/03/readme.md:
--------------------------------------------------------------------------------
1 | # Day 3 - Creating Observables the easy way!
2 |
3 | In the [previous entry](../02/readme.md), we covered a bit more about Observable creation, but once again we were in the weeds showing how the magic is made. Now that you understand the basics, let's make it easy for you going forward. Built into RxJS are a number of predefined ways of creating Observables such as:
4 |
5 | Conversions:
6 | - `bindCallback` - binds a callback to an Observable
7 | - `bindNodeCallback` - binds a Node.js style error callback to an Observable
8 | - `from` - converts a number of structures to an Observable such as Arrays, Promises, Observables, Iterables, etc.
9 | - `fromEvent`/`fromEventPattern` - converts DOM events and `EventEmitter`
10 |
11 | Creation:
12 | - `generate` - creates an Observable based upon a for loop behavior
13 | - `empty` - creates an Observable that emits only a `complete`
14 | - `interval` - creates an Observable that emits on the given time sequence
15 | - `never` - creates an Observable that never emits a value
16 | - `of` - creates an Observable from a list of arguments
17 | - `range` - creates an Observable from a range of numbers
18 | - `throwError` - creates an error Observable which throws the given error
19 | - `timer` - creates an Observable that emits at the first time, and repeats on the period time if given
20 |
21 | Today, we're going to talk a little bit about the creation operations since they are the easiest way to get started.
22 |
23 | ### The of operator
24 |
25 | The most common used way of creating Observables is via the `of` factory. This takes a number of arguments, followed by an optional Scheduler of your choosing which determines how the values are emitted.
26 |
27 | ```typescript
28 | import {
29 | asapScheduler,
30 | of
31 | } from 'rxjs';
32 |
33 | // Using the default scheduler
34 | const source1$ = of(1, 2, 3);
35 |
36 | // Using the ASAP scheduler
37 | const source2$ = of(4, 5, 6, asapScheduler);
38 | ```
39 |
40 | We can then listen for the results with a nice tracer for which sequence is which:
41 | ```typescript
42 | const observer = (id: number) => {
43 | return {
44 | next: (x: number) => console.log(`ID ${id} Next: ${x}`),
45 | complete: () => console.log(`ID ${id} Complete!`)
46 | };
47 | }
48 |
49 | const subscription1 = source1$.subscribe(observer(1));
50 | const subscription2 = source2$.subscribe(observer(2));
51 | ```
52 |
53 | Running that via `ts-node` gives us the following:
54 | ```bash
55 | $ npx ts-node 03/index.ts
56 | ID 1 Next: 1
57 | ID 1 Next: 2
58 | ID 1 Next: 3
59 | ID 1 Complete!
60 | ID 2 Next: 4
61 | ID 2 Next: 5
62 | ID 2 Next: 6
63 | ID 2 Complete!
64 | ```
65 |
66 | # The range operator
67 |
68 | Another way of creating Observables is via the `range` operator which creates a sequence of numbers with a start value, a number of elements to produce, and an optional scheduler once again to control the concurrency. You'll notice a common theme here that all creation operations take in an optional scheduler. This is for testing purposes but also allows you to control the concurrency and where it runs.
69 |
70 | ```typescript
71 | import {
72 | asapScheduler,
73 | range
74 | } from 'rxjs';
75 |
76 | // Without a scheduler
77 | const number1$ = range(0, 3);
78 |
79 | // With the asap scheduler
80 | const number2$ = range(3, 3, asapScheduler);
81 | ```
82 |
83 | Using the above subscription techniques to track which one we're listening to, let's run through the values.
84 |
85 | ```bash
86 | $ npx ts-node 03/index.ts
87 | ID 1 Next: 0
88 | ID 1 Next: 1
89 | ID 1 Next: 2
90 | ID 1 Complete!
91 | ID 2 Next: 3
92 | ID 2 Next: 4
93 | ID 2 Next: 5
94 | ID 2 Complete!
95 | ```
96 |
97 | # The generate operator
98 |
99 | Finally, let's cover one last creation operation, the `generate` function. This acts as your standard for loop like this:
100 | ```typescript
101 | for (let i = 0; /* initial value */
102 | i < 10; /* condition */
103 | i++ /* increment */) {
104 | // Do something with the i value
105 | }
106 | ```
107 |
108 | In the same way, our `generate` function acts the same way with the following arguments:
109 | ```typescript
110 | generate(
111 | initialValue: Source,
112 | condition: (value: Source) => bool
113 | iterate: (value: Source) => Source,
114 | resultSelector: (value: Source) => Result
115 | );
116 | ```
117 |
118 | Using this knowledge, we can easily emit three values such as:
119 | ```typescript
120 | import {
121 | generate
122 | } from 'rxjs';
123 |
124 | const number$ = generate(
125 | 43,
126 | i => i < 46,
127 | i => i + 1,
128 | i => String.fromCharCode(i)
129 | );
130 |
131 | const subscription = number$.subscribe({
132 | next: x => console.log(`Next: ${x}`),
133 | complete: () => console.log(`Complete!`)
134 | });
135 | ```
136 |
137 | Running our solution now will look like this where we transformed our numbers into characters based upon their character code:
138 | ```bash
139 | $ npx ts-node 03/index.ts
140 | Next: +
141 | Next: ,
142 | Next: -
143 | Complete!
144 | ```
145 |
146 | Stay tuned to tomorrow's session where we'll cover more about creation, such as values over time and conversions!
147 |
--------------------------------------------------------------------------------
/07/readme.md:
--------------------------------------------------------------------------------
1 | # Day 7 - Pipe Dreams
2 |
3 | In the [previous entry](../06/readme.md), we talked about converting existing events to RxJS Observables. Today, we're going to take our adventure a little bit further with chaining operations together.
4 |
5 | ## Adding operators the old fashioned way
6 |
7 | In previous releases, RxJS would use dot-chaining for operators, meaning we would add methods such as `map`, `filter`, `scan` and others directly to the prototype so we could achieve a nice fluent API. This had some advantages of a complete toolbox where you could easily consume new operators by adding it to the prototype.
8 |
9 | ```typescript
10 | var observable = Rx.Observable.range(0, 10)
11 | .map(x => x * x)
12 | .filter(x => x % 3 === 0);
13 | ```
14 |
15 | This had many advantages at the time with a batteries included approach where you had all the operators you usually needed directly out of the box. Unfortunately, the number of operators used in RxJS grew over time where we had to split out each operator by functionality, either by time, by join patterns, by grouping, or core. That was a little bit of a hassle because you would get many operators you would never even use.
16 |
17 | ## Lettable Operators
18 |
19 | Instead, we focused on an operator we already had with `let` which would allow you to compose operators in a bit of a nicer way, for example we could export `map` as a function and then compose as we wished.
20 |
21 | ```typescript
22 | function map(source: Observable, selector: (value: T, index: number) => R) {
23 | return new Observable(observer => {
24 | let index = 0;
25 | return source.subscribe({
26 | next: x => {
27 | let value: any;
28 | try {
29 | value = selector(x, index++);
30 | } catch (err) {
31 | observer.error(err);
32 | return;
33 | }
34 |
35 | observer.next(value);
36 | },
37 | error: err => observer.error(err),
38 | complete: () => observer.complete()
39 | })
40 | });
41 | }
42 |
43 | const obs$ = Observable.range(0, 100)
44 | .let(o => map(o, x => x * x));
45 | ```
46 |
47 | This was a step in the right direction, but having to project the source over yourself again and again was a bit of a pain. What if we could abstract away the source and have that applied later? We could then write our map function as a partially applied function.
48 |
49 | ```typescript
50 | function map(selector: (value: T, index: number) => R) {
51 | return function mapOperator(source: Observable) : Observable {
52 | return new Observable(observer => {
53 | let index = 0;
54 | return source.subscribe({
55 | next: x => {
56 | let value: any;
57 | try {
58 | value = selector(x, index++);
59 | } catch (err) {
60 | observer.error(err);
61 | return;
62 | }
63 |
64 | observer.next(value);
65 | }
66 | },
67 | error: err => observer.error(err),
68 | complete: () => observer.complete()
69 | });
70 | };
71 | }
72 |
73 | const num$ = Observable.range(0, 100)
74 | .let(map(x => x * x));
75 | ```
76 |
77 | ## Piping data all the way through
78 |
79 | That looks a bit better and towards a solution we'd want for importing only the operators we want. This style was called lettable operators. This was changed later on to `pipe` because of the confusing name around it, like what the heck does "let" even mean? Not only did this change a bit from allowing only a single operator, to allowing any number of operators, where we could write `pipe` easily enough over an arguments array of them.
80 |
81 | ```typescript
82 | function pipe(...operators: Operator) {
83 | const ops = Array.from(operators);
84 |
85 | return function piped(input: any) {
86 | return ops.reduce((prev, fn) => fn(prev), input);
87 | }
88 | }
89 | ```
90 |
91 | Then we could write something like the following where we could take map and filter together and then give it an initial value.
92 |
93 | ```typescript
94 | const pipes = pipe(
95 | map(x => x * x),
96 | filter(x => x % 3 === 0)
97 | );
98 |
99 | const pipe$ = pipes(range(, 100)).subscribe({
100 | next: x => console.log(`Next: ${x}`)
101 | });
102 | ```
103 |
104 | Luckily this is all done for you with there being a `Observable.prototype.pipe` which provides this method for you. And all the operators have been rewritten in such a way to support this style by importing from `rxjs/operators`. So now we could rewrite our above sample as easily as this with imports.
105 |
106 | ```typescript
107 | import { range } from 'rxjs';
108 | import {
109 | filter,
110 | map
111 | } from 'rxjs/operators';
112 |
113 | const num$ = range(0, 100).pipe(
114 | map(x => x * x),
115 | filter(x => x % 3 === 0)
116 | );
117 |
118 | num$.subscribe({
119 | next: x => console.log(`Next: ${x}`)
120 | });
121 | ```
122 |
123 | That's enough for now, but now gets us to a nicer spot where we can tree shake, taking only the pieces of RxJS we need, such as the `map` and `filter` operator, the `range` creation method, and that's it! Next, we'll go into some basic operators, so stay tuned!
124 |
--------------------------------------------------------------------------------
/04/readme.md:
--------------------------------------------------------------------------------
1 | # Day 4 - Creating delayed and polling operations
2 |
3 | In the [previous entry](../03/readme.md), we covered some of the creation and conversion operators. We will get to the conversion ones quickly, but today I wanted to cover two quick creation operations for creating a polling mechanism in RxJS, with both `timer` and `interval`. These are good operations for example long polling, or scheduling work items in the future. If you want, for example, a game play loop, `interval` or `timer` can be used in this scenario.
4 |
5 | # The timer operation
6 |
7 | The first operation we'll look at is the `timer` operation. This has two functions really, a way of scheduling something in the future, or a way of scheduling something in the future with a recurring period. Note that as always with creation operations, it allows for your own scheduler implementation.
8 |
9 | ```typescript
10 | timer(initial: number, scheduler?: SchedulerLike): Observable;
11 | timer(initial: number, period: number, scheduler?: SchedulerLike): Observable;
12 | ```
13 |
14 | In this first example, we will schedule an item to occur in half a second from now.
15 |
16 | ```typescript
17 | import {
18 | timer
19 | } from 'rxjs';
20 |
21 | // Schedule something once after half a second
22 | const delay = 500 /* ms */;
23 | const poll$ = timer(delay);
24 |
25 | poll$.subscribe({
26 | next: (x: number) => console.log(`Delayed by ${delay}ms item: ${x}`),
27 | complete: () => console.log('Delayed complete!')
28 | });
29 | ```
30 |
31 | Running this via `ts-node` gives us the following where it shows the first value is 0, and then it completes the stream.
32 |
33 | ```bash
34 | $ npx ts-node 04/index.ts
35 | Delayed by 500ms item: 0
36 | Delayed complete!
37 | ```
38 |
39 | The next example, we will schedule an item a half second from now, and then to run at one second intervals. Note that this operation will run infinitely, so we can truncate by unsubscribing at any point, or a simple operator we'll talk about later called `take`.
40 |
41 | ```typescript
42 | import {
43 | timer
44 | } from 'rxjs';
45 |
46 | // Schedule something once after half a second
47 | const delay = 500 /* ms */;
48 | const period = 1000 /* ms */;
49 | const poll$ = timer(delay, period);
50 |
51 | const subscription = poll$.subscribe({
52 | next: (x: number) => console.log(`Delayed by ${delay}ms item: ${x}`),
53 | complete: () => console.log('Delayed complete!')
54 | });
55 |
56 | // Unsubscribe after 4 seconds
57 | setTimeout(() => subscription.unsubscribe(), 4000);
58 | ```
59 |
60 | Running this gives us the following result which truncates after 4 seconds.
61 | ```bash
62 | $ npx ts-node 04/index.ts
63 | Delayed by 500ms item: 0
64 | Delayed by 500ms item: 1
65 | Delayed by 500ms item: 2
66 | Delayed by 500ms item: 3
67 | ```
68 |
69 | Instead of this magic unsubscription, we can simply use the `take` operator that we will cover later in our limiting Observables part and composed via the `pipe` operator.
70 |
71 | ```typescript
72 | import { timer } from 'rxjs';
73 | import { take } from 'rxjs/operators';
74 |
75 | // Schedule something once after half a second
76 | const delay = 500;
77 | const period = 1000;
78 | const poll$ = timer(delay, period);
79 |
80 | poll$
81 | .pipe(take(4))
82 | .subscribe({
83 | next: (x: number) => console.log(`Delayed by ${delay}ms item: ${x}`),
84 | complete: () => console.log('Delayed complete!')
85 | });
86 | ```
87 |
88 | Running this now gives us the following, and note that this sequence does terminate with a `complete` call unlike the `unsubscribe()` call we did above.
89 | ```bash
90 | $ npx ts-node 04/index.ts
91 | Delayed by 500ms item: 0
92 | Delayed by 500ms item: 1
93 | Delayed by 500ms item: 2
94 | Delayed by 500ms item: 3
95 | Delayed complete!
96 | ```
97 |
98 | # The interval operation
99 |
100 | The `interval` creation operation is quite straightforward to create an interval at a specified period of time.
101 |
102 | ```typescript
103 | interval(period: number, scheduler?: SchedulerLike): Observable;
104 | ```
105 |
106 | In fact, you could easily write `interval` just by using `timer` for example `timer(1000, 1000)` is the same as `interval(1000)`. We can run through the same exercise as above but instead of starting at half a second, this starts at a one second delay.
107 |
108 | ```typescript
109 | import { interval } from 'rxjs';
110 | import { take } from 'rxjs/operators';
111 |
112 | // Schedule something once after half a second
113 | const period = 1000;
114 | const poll$ = interval(period);
115 |
116 | poll$
117 | .pipe(take(4))
118 | .subscribe({
119 | next: (x: number) => console.log(`Delayed by ${period}ms item: ${x}`),
120 | complete: () => console.log('Delayed complete!')
121 | });
122 | ```
123 |
124 | Running our example once again, we can take 4 items and then truncate the observable at that point.
125 |
126 | ```bash
127 | $ npx ts-node 04/index.ts
128 | Delayed by 1000ms item: 0
129 | Delayed by 1000ms item: 1
130 | Delayed by 1000ms item: 2
131 | Delayed by 1000ms item: 3
132 | Delayed complete!
133 | ```
134 |
135 | This should start to give you some ideas on the power of RxJS and the tools at your disposal to create really interesting games and other scenarios. Stay tuned!
136 |
--------------------------------------------------------------------------------
/06/readme.md:
--------------------------------------------------------------------------------
1 | # Day 6 - Converting Events to Observables
2 |
3 | In the [previous entry](../05/readme.md), we covered converting various data structures to Observables via the `from` operation. Today, we'll continue on the conversion operations, this time with events, using `fromEvent` and `fromEventPattern`.
4 |
5 | ## Converting events to Observable with fromEvent
6 |
7 | The first operation we're going to look at here is `fromEvent` which allows us to take DOM Nodes, a DOM Node List, a Node.js `EventListener`, and even jQuery objects, and transform their events into Observables. This will attach the event during the Observable creation, and during the teardown, it will remove the event listener.
8 |
9 | Let's make this a bit more concrete with some code. Let's take for example a DOM Node, capturing click events from a button.
10 | ```typescript
11 | import { fromEvent } from 'rxjs';
12 |
13 | const element = document.querySelector('#submitButton');
14 | const click$ = fromEvent(element, 'click');
15 |
16 | click$.subscribe({
17 | next: () => console.log('Clicked');
18 | });
19 | ```
20 |
21 | We can also capture a DOM Node list and `fromEvent` will iterate over the items and subscribe to the `click` event for each one.
22 | ```typescript
23 | import { fromEvent } from 'rxjs';
24 |
25 | const elements = document.querySelectorAll('.selectButtons');
26 | const click$ = fromEvent(element, 'click');
27 |
28 | click$.subscribe({
29 | next: () => console.log('Clicked');
30 | });
31 | ```
32 |
33 | We also bridge to the Node.js `EventEmitter` so capturing data is pretty quick and easy!
34 |
35 | ```typescript
36 | import { EventEmitter } from 'events';
37 | import { fromEvent } from 'rxjs';
38 |
39 | const emitter = new EventEmitter();
40 | const event$ = fromEvent(emitter, 'data');
41 |
42 | emitter.emit('data', 'foobarbaz');
43 |
44 | event$.subscribe({
45 | next: x => console.log(`Data: ${x}`)
46 | });
47 |
48 | for (let i of ['foo', 'bar', 'baz', 'quux']) {
49 | emitter.emit('data', i);
50 | }
51 | ```
52 |
53 | What you'll notice in this example, we will completely miss the first event with the data `'foobarbaz'` because we had not yet subscribed to our Observable. This is often the cause of a lot of confusion. One workaround for this is to add `shareReplay` which will record all data and replay to every subscriber. We will cover the whole "share" aspect later on in this series. Running the above sample will give us the expected output of 'foo', 'bar', 'baz' and 'quux'.
54 | ```bash
55 | $ npx ts-node 06/index.ts
56 | Data: foo
57 | Data: bar
58 | Data: baz
59 | Data: quux
60 | ```
61 |
62 | Let's get a little bit more advanced where we want to completely convert an `EventEmitter` to an Observable, where we can capture not only the 'data' event, but also react to the 'error' and 'close' events as well. We would need a way to throw the error if an error happens in the 'error' event. We can merge these two streams together using the `merge` operation which creates a single observable which encompasses all two events.
63 |
64 | ```typescript
65 | import { EventEmitter } from 'events';
66 | import {
67 | fromEvent,
68 | throwError
69 | } from 'rxjs';
70 | import { mergeMap } from 'rxjs/operators';
71 |
72 | const emitter = new EventEmitter();
73 | const data$ = fromEvent(emitter, 'data');
74 |
75 | const error$ = fromEvent(emitter, 'error')
76 | .pipe(mergeMap(err => throwError(err)));
77 |
78 | const merged$ = merge(data$, error$);
79 | ```
80 |
81 | Now let's run this through with some data to see what happens!
82 | ```typescript
83 | merged$.subscribe({
84 | next: x => console.log(`Data: ${x}`),
85 | error: x => console.log(`Error: ${x}`)
86 | });
87 |
88 | for (let i of ['foo', 'bar', 'baz', 'quux']) {
89 | emitter.emit('data', i);
90 | }
91 | emitter.emit('error', new Error('woops'));
92 | ```
93 |
94 | And running it at the console gives the results where we capture not only the data, but an error as well.
95 | ```bash
96 | $ npx ts-node 06/index.ts
97 | Data: foo
98 | Data: bar
99 | Data: baz
100 | Data: quux
101 | Error: Error: woops
102 | ```
103 |
104 | ## Converting events to Observables with fromEventPattern
105 |
106 | Sometimes, our event-based contract is not quite as straightforward as the DOM, Node.js `EventEmitter` or jQuery. Using `fromEventPattern` you can encompass your own way of attaching and detaching handlers from your event emitter, whatever it may be. For example, you could model if your API uses `attachEvent` and `detachEvent` as the subscription pair.
107 |
108 | ```typescript
109 | import { fromEventPattern } from 'rxjs';
110 |
111 | const event$ = fromEventPattern(
112 | h => obj.attachEvent('data', h),
113 | h => obj.detachEvent('data', h)
114 | );
115 | ```
116 |
117 | We can also encompass an API that returns a token for example for unsubscription for example an `AbortController`. By returning it from the add handler, we can then use it in our remove handler by passing it in as the second argument.
118 |
119 | ```typescript
120 | import { fromEventPattern } from 'rxjs';
121 |
122 | const event$ = fromEventPattern(
123 | h => { return obj.registerListener('data', h); },
124 | (h, controller) => { controller.abort(); }
125 | );
126 | ```
127 |
128 | That's enough for today! Join me tomorrow for our next adventure where we're getting into the `pipe` finally!
129 |
--------------------------------------------------------------------------------
/18/readme.md:
--------------------------------------------------------------------------------
1 | # Day 18 - Getting distinct data
2 |
3 | In the [previous entry](../17/readme.md), we explored how we can create an autosuggest scenario based upon the user input, such as coming from an event stream. Today, we're going to look at how you can get unique data, as well as unique data until the data changes.
4 |
5 | ## Only distinct data
6 |
7 | The first operator we're going to look at is getting distinct data via the `distinct` operator. This allows us to get data that when compared to others is distinct. For example, we can get distinct numbers.
8 |
9 | ```typescript
10 | import { Subject } from 'rxjs';
11 | import { distinct } from 'rxjs/operators';
12 |
13 | const num$ = new Subject();
14 |
15 | num$
16 | .pipe(distinct())
17 | .subscribe({ next: item => console.log(`Distinct Item: ${item}`) });
18 |
19 | num$.next(1);
20 | // Distinct Item 1
21 | num$.next(2);
22 | // Distinct Item 2
23 | num$.next(1);
24 | num$.next(2);
25 | num$.next(3);
26 | // Distinct Item 3
27 | ```
28 |
29 | As you'll notice, it's emitting only distinct values as they come along in our stream, getting rid of duplicates. Now, it's not super realistic obviously to have just number data, but what if instead we allowed you to pick a key to use as the distinct selector?
30 |
31 | ```typescript
32 | interface Person { name: string }
33 |
34 | const person$ = new Subject();
35 |
36 | person$
37 | .pipe(distinct(item => item.name))
38 | .subscribe({ next: item => console.log(`Distinct Item: ${item.name}`) });
39 |
40 | person$.next({ name: 'Bob' });
41 | // Distinct Item: Bob
42 | person$.next({ name: 'Mary' });
43 | // Distinct Item: Mary
44 | person$.next({ name: 'Bob' });
45 | person$.next({ name: 'Frank' });
46 | // Distinct Item: Frank
47 | ```
48 |
49 | Note that since this is collecting distinct values in an internal hash set, this could grow infinitely large in size over time. There is an optional overload which takes an Observable, which when fires, clears the internal cache. For example, we could specify an Observable that fires every five seconds, which clears the cache, so we have a rolling window of distinct values.
50 |
51 | ```typescript
52 | import { Subject, interval } from 'rxjs';
53 | import { distinct } from 'rxjs/operators';
54 |
55 | const num$ = new Subject();
56 |
57 | num$
58 | .pipe(distinct(undefined, interval(5000)))
59 | .subscribe({ next: item => console.log(`Distinct Item: ${item}`) });
60 | ```
61 |
62 | ## Streaming distinct
63 |
64 | Having unique values is definitely very useful in reactive programming, but what about firing only when the data changes from the previous value? An example of this would be capturing keyboard input from a DOM element and projecting the text from the input. The problem of course is that you could hit the arrow keys, and although the 'keyup' event is firing, our data has not in fact changed.
65 |
66 | ```typescript
67 | import { fromEvent } from 'rxjs';
68 | import { distinctUntilChanged, map } from 'rxjs/operators';
69 |
70 | const input$ = fromEvent(document.querySelector('#input', 'keyup'))
71 | .pipe(
72 | map(ev => ev.target.value),
73 | distinctUntilChanged()
74 | );
75 | ```
76 |
77 | This will then filter out the unwanted noise from the keys and other items that don't actually change the value at all. We can demonstrate that here as well with a `Subject` emitting data.
78 |
79 | ```typescript
80 | import { Subject } from 'rxjs';
81 | import { distinctUntilChanged } from 'rxjs/operators';
82 |
83 | const num$ = new Subject();
84 |
85 | num$
86 | .pipe(distinctUntilChanged())
87 | .subscribe({ next: item => console.log(`Distinct Item: ${item}`) });
88 |
89 | num$.next(1);
90 | // Distinct Item: 1
91 | num$.next(1);
92 | num$.next(2);
93 | // Distinct Item: 2
94 | num$.next(1);
95 | // Distinct Item: 1
96 | num$.next(2);
97 | // Distinct Item: 2
98 | num$.next(2);
99 | num$.next(2);
100 | ```
101 |
102 | You'll note from this example that we only emit when our new data is different from our previous, and not overall distinct. We can also give a comparator function which allows us to compare the previous and the current item to see if they are equal.
103 |
104 | ```typescript
105 | interface Person { name: string }
106 |
107 | const person$ = new Subject();
108 |
109 | person$
110 | .pipe(distinctUntilChanged((prev, curr) => prev.name === curr.name))
111 | .subscribe({ next: item => console.log(`Distinct Item: ${item.name}`) });
112 |
113 | person$.next({ name: 'Bob' });
114 | // Distinct Item: Bob
115 | person$.next({ name: 'Bob' });
116 | person$.next({ name: 'Mary' });
117 | // Distinct Item: Mary
118 | person$.next({ name: 'Mary' });
119 | person$.next({ name: 'Frank' });
120 | // Distinct Item: Frank
121 | ```
122 |
123 | Finally, there is another way of comparing with a given key, for example, we could extract the 'name' from our data and use that as a comparator.
124 |
125 | ```typescript
126 | interface Person { name: string }
127 |
128 | const person$ = new Subject();
129 |
130 | person$
131 | .pipe(distinctUntilKeyChanged('name'))
132 | .subscribe({ next: item => console.log(`Distinct Item: ${item.name}`) });
133 |
134 | person$.next({ name: 'Bob' });
135 | // Distinct Item: Bob
136 | person$.next({ name: 'Bob' });
137 | person$.next({ name: 'Mary' });
138 | // Distinct Item: Mary
139 | person$.next({ name: 'Mary' });
140 | person$.next({ name: 'Frank' });
141 | // Distinct Item: Frank
142 | ```
143 |
144 | So, this is one technique for taming user input for our autosuggest scenario. We'll cover time based ones tomorrow, so stay tuned!
145 |
--------------------------------------------------------------------------------
/10/readme.md:
--------------------------------------------------------------------------------
1 | # Day 10 - Starting with data, ending with data, and defaulting if empty
2 |
3 | In the [previous entry](../09/readme.md), we covered aggregation operations via `reduce` and `scan`. Today, we're going to dive deeper into some operators where you can add that initial state to your observable, as well as add anything to the end. And in the cases where your data stream is empty, we can accommodate for that too with yet another operator.
4 |
5 | ## Starting off with startWith
6 |
7 | The first operator we're going to look at is `startWith`, which allows us to prepend data to the beginning of an Observable sequence. As with other operators that create things, this also takes a scheduler which allows you to control the concurrency.
8 |
9 | ```typescript
10 | startWith(...args: (Array | SchedulerLike)): Observable;
11 | ```
12 |
13 | We could easily write this using this for Iterables which would show you basically how `startWith` works.
14 |
15 | ```typescript
16 | function* startWith(source: Iterable, ...args: Array) {
17 | for (let i = 0; i < args.length; i++) {
18 | yield args[i];
19 | }
20 |
21 | for (let item of source) {
22 | yield item;
23 | }
24 | }
25 |
26 | startWith([2, 3, 4], 0, 1);
27 | // [0, 1, 2, 3, 4]
28 | ```
29 |
30 | So, we can take an existing sequence and prepend any number of values to it. Once again like all operators, we can implement them in terms of another, for example using `concat` and `of`. That's the beauty of RxJS, is that it's easy to make your own custom operator just as if they were Lego blocks.
31 |
32 | ```typescript
33 | function startWith(source: Observable, ...args: Array) {
34 | return concat(of(...args), source);
35 | }
36 | ```
37 |
38 | Let's add some values to the beginning of this sequence!
39 |
40 | ```typescript
41 | import { of } from 'rxjs';
42 | import { startWith } from 'rxjs/operators';
43 |
44 | const num$ = of(2, 3, 4).pipe(
45 | startWith(0, 1)
46 | );
47 |
48 | num$.subscribe({
49 | next: x => console.log(`Next: ${x}`)
50 | });
51 | // Next 0
52 | // Next 1
53 | // Next 2
54 | // Next 3
55 | // Next 4
56 | ```
57 |
58 | This is useful of course if you want to seed your stream with default data, such as an initial state for mouse movements with an x and y coordinate.
59 |
60 | ## Ending our sequence with endWith
61 |
62 | Just as we have `startWith` which allows us to prepend values to the front of the Observable, we also have `endWith` which appends values to the end of the Observable sequence. For a while, I had resisted such an operator, given how sequences must have some sort of termination in order to add values to the end. Would it make sense to end a stream of mouse moves with more values? After a while, I relented, and we added `endWith` which allows us once again like `startWith` to add arbitrary values with a Scheduler.
63 |
64 | ```typescript
65 | endWith(...args: (Array | SchedulerLike)): Observable;
66 | ```
67 |
68 | We could implement this for Iterables once again to show you how it works at least for pull sequences, iterating first over our source sequence, then appending all the arguments given in the function.
69 |
70 | ```typescript
71 | function* endWith(source: Iterable, ...args: Array) {
72 | for (let item of source) {
73 | yield item;
74 | }
75 |
76 | for (let i = 0; i < args.length; i++) {
77 | yield args[i];
78 | }
79 | }
80 | ```
81 |
82 | Like above, we could implement this in RxJS instead of building from scratch, simply implement via the following using `concat` and `of`.
83 |
84 | ```typescript
85 | import { concat, of } from 'rxjs';
86 |
87 | function endWith(source: Observable, ...args: Array) {
88 | return concat(source, of(...args)));
89 | }
90 | ```
91 |
92 | An example of adding to the end could be like the following where we add 3 and 4 to an Observable of 1 and 2.
93 |
94 | ```typescript
95 | import { of } from 'rxjs';
96 | import { endWith } from 'rxjs/operators';
97 |
98 | const num$ = of(1, 2).pipe(
99 | endWith(3, 4)
100 | );
101 |
102 | num$.subscribe({
103 | next: x => console.log(`Next: ${x}`)
104 | });
105 | // Next 1
106 | // Next 2
107 | // Next 3
108 | // Next 4
109 | ```
110 |
111 | ## What to do when you are empty
112 |
113 | In some cases, our Observable stream may be empty, whether it is due to connectivity, an empty cache or so on. In RxJS, we have a way of getting around this by allowing you to substitute a value if the sequence is empty. This is helpful for example if you don't have any data, and instead want to return some cached data or some skeleton data.
114 |
115 | We could implement this using Iterables just to give you an idea how this might work with Observables where we have an internal state flag which says whether it has yielded any data, and if it hasn't, then yield the default value.
116 |
117 | ```typescript
118 | function* defaultIfEmpty(source: Iterable, defaultValue: T) {
119 | let state = 1;
120 | for (let item of source) {
121 | state = 2;
122 | yield item;
123 | }
124 | if (state === 1) {
125 | yield defaultValue;
126 | }
127 | }
128 | ```
129 |
130 | Bringing this all together, we could take an empty sequence with a default, prepend some data to it, and then add some data to it at the end.
131 |
132 | ```typescript
133 | empty().pipe(
134 | defaultIfEmpty(42),
135 | startWith(0, 1),
136 | endWith(4, 5)
137 | )
138 | .subscribe(x => console.log(x));
139 | // 0
140 | // 1
141 | // 42
142 | // 4
143 | // 5
144 | ```
145 |
146 | That's enough for now, but I hope this gives you some idea of what you can do to build operators on your own using the building blocks we have! Next up, we'll implement our own Redux and then spend the next days spending explaining how we did it! Stay tuned!
147 |
--------------------------------------------------------------------------------
/09/readme.md:
--------------------------------------------------------------------------------
1 | # Aggregating via reduce and scan
2 |
3 | In the [previous entry](../08/readme.md), we covered the basic operators that we would find on `Array` like `map` and `filter` are also there in for Observables and act pretty much the same way! There's another common operator, `reduce` that takes a value and aggregates it by folding over the data until it gets to a single value. This `reduce` method is two forms, one that provides an initial seed value for the accumulator, and another that doesn't. We could implement this for Iterables like Arrays pretty easily such as the following code, accounting for both a seed value and no seed value. If there is no seed value and the iterable is empty, then we throw an error because we cannot determine what the end value might be.
4 |
5 | ```typescript
6 | function reduce(source: Iterable, accumulator: (acc: R, value: T, index: number) => R), seed?: R): R {
7 | const hasSeed = seed.length === 1;
8 | let i = 0,
9 | hasValue = false,
10 | acc = seed[0] as T | R;
11 | for (let item of source) {
12 | if (hasValue || (hasValue = hasSeed)) {
13 | acc = accumulator(acc, item, i++);
14 | } else {
15 | acc = item;
16 | hasValue = true;
17 | i++;
18 | }
19 | }
20 |
21 | if (!(hasSeed || hasValue)) {
22 | throw new Error('Sequence contains no elements');
23 | }
24 |
25 | return acc as R;
26 | }
27 |
28 | const sum = reduce([1, 2, 3], (acc, current) => acc + current, 0);
29 | // 6
30 | ```
31 |
32 | With RxJS, for composition purposes, we do not reduce to a single value such as a `Promise` but instead reduce it to an Observable with a single value. Optionally, we could do away with the whole throwing an error on empty observables and instead return an empty Observable.
33 |
34 | ```typescript
35 | import { Observable } from 'rxjs';
36 |
37 | function reduce(accumulator: (acc: R, value: T, index: number) => R), seed?: R): Observable {
38 | const hasSeed = arguments.length === 2;
39 | return function reduceOperator(source: Observable): Observable {
40 | return new Observable(observer => {
41 | let hasValue = false,
42 | acc = seed as T | R;
43 |
44 | return source.subscribe({
45 | next: item => {
46 | if (hasValue || (hasValue = hasSeed)) {
47 | acc = accumulator(acc, item);
48 | } else {
49 | acc = item;
50 | hasValue = true;
51 | }
52 | },
53 | error: err => observer.error(err),
54 | complete: () => {
55 | if (!(hasSeed || hasValue)) {
56 | observer.complete();
57 | return;
58 | }
59 |
60 | observer.next(acc as R);
61 | observer.complete();
62 | }
63 | })
64 | });
65 | };
66 | }
67 | ```
68 |
69 | Much as before, we have `reduce` already implemented for us in `rxjs/operators` so none of this is required, but it's good to know how the sausage is made sometimes.
70 |
71 | ```typescript
72 | import { of } from 'rxjs';
73 | import { reduce } from 'rxjs/operators';
74 |
75 | const num$ = of(1, 2, 3).pipe(
76 | reduce((acc, x) => acc + x, 0);
77 | );
78 |
79 | num$.subscribe({
80 | next: x => console.log(`Next: ${x}`)
81 | });
82 | // Next: 6
83 | ```
84 |
85 | Aggregations such as `reduce` and others aren't super useful as the sequence must have some sort of termination in order to work. Many Observables such as mouse movements don't necessarily end as they can be infinite streams, so what good is having `reduce`? Well, what if we told you there was such a thing as `scan` which does incremental emitting of data as it accumulates data?
86 |
87 | ## Incremental accumulators via scan
88 |
89 | As I said before, aggregate operations aren't necessarily useful especially when dealing with infinite streams of data. Instead, what if could emit intermediate values as part of the accumulation? A useful example of this would be counting mouse clicks, where our `scan` operator is basically a state machine. We could implement this for Iterables just as we did for reduce above.
90 |
91 | ```typescript
92 | function scan(source: Iterable, accumulator: (acc: R, current, T) => R, seed?: R) {
93 | let hasValue = false,
94 | acc = this._seed;
95 | for (let item of source) {
96 | if (hasValue || (hasValue = this._hasSeed)) {
97 | acc = accumulator(acc, item);
98 | yield acc;
99 | } else {
100 | acc = item;
101 | hasValue = true;
102 | }
103 | }
104 | }
105 |
106 | scan([1, 2, 3], (acc, x) => acc + x, 0);
107 | // 1
108 | // 3
109 | // 6
110 | ```
111 |
112 | Implementing this using Observables is pretty much as straight forward as the iterables approach, and not that much different than what we did for `reduce` except that we're not caring about the completion as much in order to emit values.
113 |
114 | ```typescript
115 | function scan(accumulator: (acc: R, curr: T) => R, seed?: R) {
116 | return function scanOperator(source: Observable): Observable {
117 | const hasSeed = arguments.length === 2;
118 | return new Observable(observer => {
119 | let hasValue = false,
120 | acc = seed as T | R;
121 |
122 | return source.subscribe({
123 | next: item => {
124 | let result: any;
125 | if (hasValue || (hasValue = this._hasSeed)) {
126 | try {
127 | result = accumulator(seed, item);
128 | } catch (err) {
129 | observer.error(err);
130 | return;
131 | }
132 |
133 | seed = result;
134 | observer.next(result);
135 | } else {
136 | seed = item;
137 | observer.next(item);
138 | }
139 | },
140 | error: err => observer.error(err),
141 | complete: () => observer.complete()
142 | });
143 | });
144 | };
145 | }
146 | ```
147 |
148 | Now let's look at an example where this might be useful instead of just simple addition on lists, what about counting mouse button clicks?
149 |
150 | ```typescript
151 | import { fromEvent } from 'rxjs';
152 | import { scan } from 'rxjs/operators';
153 |
154 | const click$ = fromEvent(document, 'click').pipe(
155 | scan((acc, x) => acc + x, 0)
156 | );
157 |
158 | click$.subscribe({
159 | next: console.log(`Number of clicks so far: ${x}`)
160 | });
161 | ```
162 |
163 | This idea of the incremental state machine is a driving factor behind such libraries as [Redux](https://redux.js.org/) with its concept of reducers. In fact, you could probably implement the basic concepts of Redux reducers in a single line of RxJS as seen in [this post](http://rudiyardley.com/redux-single-line-of-code-rxjs/) doing nothing more than this:
164 | ```typescript
165 | action$
166 | .pipe(scan(reducer, initialState))
167 | .subscribe(renderer);
168 | ```
169 |
170 | Once again, this shows how RxJS gives you many building blocks to create rich experiences, whether it's clicking mouse buttons and tracking state, to Redux style reducers, RxJS has you covered. In the next post, we'll go deeper in other operators you'll need to know!
--------------------------------------------------------------------------------
/19/readme.md:
--------------------------------------------------------------------------------
1 | # Day 19 - Throttling and Debouncing Observables
2 |
3 | In the [previous entry](../18/readme.md), we talked about taming user input via the `distinct` and `distinctUntilChanged` operators. Today, we're going to look at another aspect of taming user input, this time with `throttle` and `debounce`.
4 |
5 | ## What's the Difference
6 |
7 | There's a fundamental difference between `throttle` and `debounce`, although the words are similar in nature.
8 |
9 | Throttle
10 | > The throttle operation enforces a maximum number of times a function can be called over time. For example, an HTTP request can be made at most once every 500 milliseconds.
11 |
12 | Debounce
13 | > The debounce operation enforces that a function is not called again until a certain amount of time has passed without it being called. For example, make an HTTP request if only 500 milliseconds have passed without it being called.
14 |
15 | ## Throttle
16 |
17 | Now that we have the basic understanding of what `throttle` is versus `debounce`, let's look at a sample implementation of it in JavaScript. First we'll need to determine when the function was called. Then we'll return the throttled function which takes the current time and checks if it's within the time constraint. If it is still within the window, the function exits without invoking the function. Otherwise, the last time the function is invoked is updated and finally the function is invoked.
18 |
19 | ```typescript
20 | function throttle(delay: number, fn: Function) {
21 | let lastCall = 0;
22 | return function throttledFunction(...args: any[]) {
23 | const now = +new Date();
24 | if (now - lastCall < delay) {
25 | return;
26 | }
27 | lastCall = now;
28 | return fn(...args);
29 | }
30 | }
31 |
32 | import { EventEmitter } from 'events';
33 |
34 | const emitter = new EventEmitter();
35 |
36 | const dataHandler = (arg: string) => console.log(`Next: ${arg}`);
37 | const throttledHandler = throttle(500, dataHandler);
38 |
39 | emitter.addListener('data', throttledHandler);
40 | ```
41 |
42 | In RxJS, we have two ways of dealing with throttling behavior, either by throttling by time in `throttleTime`, or by throttling by another Observable via `throttle`. We can model our example above using RxJS the very same way.
43 |
44 | ```typescript
45 | import { fromEvent } from 'rxjs';
46 | import { throttleTime } from 'rxjs/operators';
47 | import { EventEmitter } from 'events';
48 |
49 | const emitter = new EventEmitter();
50 |
51 | const event$ = fromEvent(emitter, 'data')
52 | .pipe(throttleTime(500));
53 |
54 | const subscription = event$.subscribe({
55 | next: item => console.log(`Next: ${item}`)
56 | });
57 |
58 | emitter.emit('data', 'foo');
59 | // Next: foo
60 | emitter.emit('data', 'bar');
61 | emitter.emit('data', 'baz');
62 | ```
63 |
64 | We also have the opportunity to put in any Observable we wish as the throttling agent, for example, it could use the `interval` operator, or even our own custom `Subject` by using the `throttle` operator.
65 |
66 | ```typescript
67 | import { fromEvent, Subject } from 'rxjs';
68 | import { throttle } from 'rxjs/operators';
69 | import { EventEmitter } from 'events';
70 |
71 | const emitter = new EventEmitter();
72 |
73 | const throttle$ = new Subject();
74 |
75 | const event$ = fromEvent(emitter, 'data')
76 | .pipe(throttle(() => throttle$));
77 |
78 | const subscription = event$.subscribe({
79 | next: item => console.log(`Next: ${item}`)
80 | });
81 |
82 | // At some point fire a next for the throttler
83 | throttle$.next();
84 | ```
85 |
86 | Now that we understand the basics of throttling, let's dive into debouncing data.
87 |
88 | ## Debounce
89 |
90 | In order to fully understand debounce, let's look at what a sample implementation might look like in JavaScript for `debounce`. In essence, we are turning any given function into an asynchronous call via `setTimeout` to ensure we call the function later if no other input has come in. So, we can take the `EventEmitter` example we have above and debounce it in a way that it will emit if no other events come within half a second.
91 |
92 | ```typescript
93 | function debounce(delay: number, fn: Function) {
94 | let timerId: NodeJS.Timeout;
95 | return function debounceFunction(...args: any[]) {
96 | if (timerId) {
97 | clearTimeout(timerId);
98 | }
99 | timerId = setTimeout(() => {
100 | fn(...args);
101 | timerId = null;
102 | }, delay);
103 | }
104 | }
105 |
106 | import { EventEmitter } from 'events';
107 |
108 | const emitter = new EventEmitter();
109 |
110 | const dataHandler = (arg: string) => console.log(`Next: ${arg}`);
111 | const debounceHandler = debounce(500, dataHandler);
112 |
113 | emitter.addListener('data', debounceHandler);
114 |
115 | emitter.emit('data', 'foobarbaz');
116 | ```
117 |
118 | Just like with `throttle`, we have two ways of dealing with debouncing behavior, `debounceTime`, which allows you to set a relative time for debouncing, and `debounce` which allows you to put in any selector which returns an Observable.
119 |
120 | ```typescript
121 | import { fromEvent } from 'rxjs';
122 | import { debounceTime } from 'rxjs/operators';
123 | import { EventEmitter } from 'events';
124 |
125 | const emitter = new EventEmitter();
126 |
127 | const event$ = fromEvent(emitter, 'data')
128 | .pipe(debounceTime(500));
129 |
130 | const subscription = event$.subscribe({
131 | next: item => console.log(`Next: ${item}`)
132 | });
133 |
134 | emitter.emit('data', 'foo');
135 | emitter.emit('data', 'bar');
136 | emitter.emit('data', 'baz');
137 | // Next: baz
138 | ```
139 |
140 | We also have the opportunity to put in any Observable we wish as the debouncing agent, for example, it could use the `interval` operator, or even our own custom `Subject` by using the `debounce` operator.
141 |
142 | ```typescript
143 | import { fromEvent, interval } from 'rxjs';
144 | import { throttle } from 'rxjs/operators';
145 | import { EventEmitter } from 'events';
146 |
147 | const emitter = new EventEmitter();
148 |
149 | const event$ = fromEvent(emitter, 'data')
150 | .pipe(throttle(() => interval(500)));
151 |
152 | const subscription = event$.subscribe({
153 | next: item => console.log(`Next: ${item}`)
154 | });
155 | ```
156 |
157 | ## Putting it all together
158 |
159 | Now that we have an understanding of `distinctUtilChanged` and `debounceTime` to tame our user input and `switchMap` to ensure we get our proper order of results, we could write our autosuggest example like this.
160 |
161 | ```typescript
162 | import { fromEvent } from 'rxjs';
163 | import {
164 | debounceTime,
165 | distinctUntilChanged,
166 | map,
167 | switchMap
168 | } from 'rxjs/operators';
169 |
170 | const value$ = fromEvent(document.querySelector('#input'), 'keyup').pipe(
171 | map(e => e.target.value),
172 | debounceTime(500),
173 | distinctUntilChanged()
174 | );
175 |
176 | const result$ = value$.pipe(
177 | switchMap(queryWikipedia)
178 | );
179 |
180 | const subscription = result$.subscribe({
181 | next: items => console.log(`Results: ${items}`)
182 | });
183 | ```
184 |
185 | Now we have a basic understanding of taming user input, combining sequences, and more! Stay tuned for much more in our journey of discovering RxJS!
--------------------------------------------------------------------------------
/05/readme.md:
--------------------------------------------------------------------------------
1 | # Day 5 - Converting to Observables
2 |
3 | In the [previous entry](../04/readme.md), we covered creating polling and timed operations via `timer` and `interval`. Today, we're still covering Observable creation, this time, by converting existing data structures to Observables via the `from` operation. This `from` operation matches the [`Array.from`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) method which was new as of ES2015.
4 |
5 | ## Using the from operation
6 |
7 | Pretty much everything can be turned into an Observable with RxJS using the `from` creation operation. It can convert an `Array`, an `Iterable` such as a `Generator`, `Set`, or a `Map`, a `Promise`, and even another `Observable`. Like all other creation operations, this also takes a `SchedulerLike` object used for scheduling the operation. By default, if not provided, the `from` operation will perform things synchronously.
8 |
9 | Internally, RxJS uses a number of operations to convert our incoming data such as:
10 | - `fromArray` - converts an array
11 | - `fromIterable` - Converts a sequence which exposes the `Symbol.iterator` property
12 | - `fromObservable` - Converts an Observable which exposes the `Symbol.observable` property
13 | - `fromPromise` - converts a `Promise`
14 |
15 | ## Converting an Array to an Observable
16 |
17 | The first conversion we can do is from an `Array` or array-like structure, meaning it has a length property. Let's first convert using just an array.
18 |
19 | ```typescript
20 | import { from } from 'rxjs';
21 |
22 | const array = [1, 2, 3];
23 | const array$ = from(array);
24 |
25 | const subscription = array$.subscribe({
26 | next: x => console.log(`Next: ${x}`),
27 | complete: () => console.log(`Complete!`)
28 | });
29 | ```
30 |
31 | Running this gives us the following results:
32 | ```bash
33 | $ npx ts-node 05/index.ts
34 | Next: 1
35 | Next: 2
36 | Next: 3
37 | Complete
38 | ```
39 |
40 | We can also run it with an array-like structure as well with a length property of 3.
41 |
42 | ```typescript
43 | const arrayLike = { length: 3 };
44 | const array$ = from(arrayLike);
45 |
46 | array$.subscribe({
47 | next: x => console.log(`Next: ${x}`),
48 | complete: () => console.log('Complete')
49 | });
50 | ```
51 |
52 | Running this gives particular version gives us the following results with `undefined` everywhere.
53 |
54 | ```bash
55 | $ npx ts-node 05/index.ts
56 | Next: undefined
57 | Next: undefined
58 | Next: undefined
59 | Complete
60 | ```
61 |
62 | Unfortunately, in later versions, the result selector function was removed from the `from` operation, but we can easily get this back via using the `map` operator which we will cover later. The `map` operator simply takes the current value and allows you to project a new value in the stream in its place. In this instance, we're more concerned with the index argument from the selector which will give us our unique place.
63 |
64 | ```typescript
65 | import { from } from 'rxjs';
66 | import { map } from 'rxjs/operators';
67 |
68 | const array = [1, 2, 3];
69 | const array$ = from(array)
70 | .pipe(map((_, i) => i));
71 |
72 | const subscription = array$.subscribe({
73 | next: x => console.log(`Next: ${x}`),
74 | complete: () => console.log(`Complete!`)
75 | });
76 | ```
77 |
78 | Now, since we're projecting the index, we now have some data to display.
79 | ```bash
80 | $ npx ts-node 05/index.ts
81 | Next: 0
82 | Next: 1
83 | Next: 2
84 | Complete!
85 | ```
86 |
87 | ## Converting an Iterable to an Observable
88 |
89 | With ES2015 came the advent of [Iterables, Iterators, and Generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators). This gave us a way to iterate over existing sequences such as arrays, but also new structures such as `Map` and `Set`. Each of these objects now implement the `Symbol.iterator` property which returns an `Iterator` object. This `Iterator` object has three methods which correspond directly to our `Observer` with `next()`, `throw()` and `return()` and its Observer equivalents with `next()`, `error()`, and `complete()`. When `next()` is called on an iterator, it returns a structure with both a property of the current value, if any, and a done flag indicating whether the sequence has been iterated. Generators also implement this `Symbol.iterator` property which allows us to create infinite sequences if we so choose.
90 |
91 | We can for example, create an Observable from a `Set` with a few values.
92 | ```typescript
93 | const set = new Set([1, 2, 3]);
94 | const set$ = from(set);
95 |
96 | const subscription = set$.subscribe({
97 | next: x => console.log(`Next: ${x}`),
98 | complete: () => console.log(`Complete!`)
99 | });
100 | ```
101 |
102 | And when we run the example, we get the following output from the sequence.
103 | ```bash
104 | $ npx ts-node 05/index.ts
105 | Next: 1
106 | Next: 2
107 | Next: 3
108 | Complete!
109 | ```
110 |
111 | We can also create a `Generator` function which yields values as well via the `yield` keyword.
112 |
113 | ```typescript
114 | const generatorFunction = function* () {
115 | yield 1;
116 | yield 2;
117 | yield 3;
118 | };
119 |
120 | const iterable$ = from(generatorFunction(), asapScheduler);
121 | iterable$.subscribe({
122 | next: x => console.log(`Next: ${x}`),
123 | complete: () => console.log('Complete')
124 | });
125 | ```
126 |
127 | And just as above, we get the results of 1, 2, 3.
128 | ```bash
129 | $ npx ts-node 05/index.ts
130 | Next: 1
131 | Next: 2
132 | Next: 3
133 | Complete!
134 | ```
135 |
136 | ## Converting an Observable to an Observable
137 |
138 | Another thing we can convert is an Observable to yet another Observable. There are a number of libraries that implement the Observable contract which has the `subscribe` method, just as the `Promise` exposes the `then` method. For example, we could take something from Redux and convert it to an RxJS Observable. In this example, we'll just use another RxJS Observable, but it could be anything that implements the `Observable` contract.
139 |
140 | ```typescript
141 | import { from, Observable } from 'rxjs';
142 |
143 | const obs$ = new Observable(observer => {
144 | observer.next(42);
145 | observer.complete();
146 | });
147 |
148 | obs$.subscribe({
149 | next: x => console.log(`Next: ${x}`),
150 | complete: () => console.log('Complete')
151 | });
152 | ```
153 |
154 | Running this sample, we get the following output:
155 | ```bash
156 | $ npx ts-node 05/index.ts
157 | Next: 42
158 | Complete!
159 | ```
160 |
161 | ## Converting a Promise to an Observable
162 |
163 | One of the more common scenarios using `from` is converting a `Promise` to an Observable. Many libraries now implement Promises as part of their asynchronous APIs, including Node.js, so it's only natural we give an easy way to convert that a `Promise` to an `Observable`.
164 |
165 | ```typescript
166 | const promise = Promise.resolve(42);
167 | const promise$ = from(promise);
168 |
169 | promise$.subscribe({
170 | next: x => console.log(`Next: ${x}`),
171 | complete: () => console.log('Complete')
172 | });
173 | ```
174 |
175 | Like above with our Observable example, we get the same output of 42 and then a completion.
176 | ```bash
177 | $ npx ts-node 05/index.ts
178 | Next: 42
179 | Complete!
180 | ```
181 |
182 | That's enough for now, but stay tuned as we cover converting events to Observables!
183 |
--------------------------------------------------------------------------------
/11/readme.md:
--------------------------------------------------------------------------------
1 | # Day 11 - On the Subject of Subjects
2 |
3 | In the [previous entry](../10/readme.md), we covered dealing with prepending, appending and substituting data if the stream is empty. Before we get started with our Redux clone, I wanted to talk briefly on the subject of Subjects. As we've talked about earlier, we have a number of concepts:
4 |
5 | - `Observable`: The producer of data that can emit data zero to infinite times. This has a contract of `subscribe` which takes an `Observer` to emit data to.
6 | - `Observer`: The data sink in which to emit the `Observable` data. This can be emitted to the `next` method zero to infinite times, optionally followed by an error via `error` or completion via `complete`. Calling the `subscribe` method on the `Observable` with the `Observer` gives us a `Subscription`.
7 | - `Subscription`: Contains the teardown logic which can be composed together, for example, multiple streams can be added together, giving a subscription, that can be torn down via `unsubscribe`.
8 |
9 | ## Broaching the Subject
10 |
11 | We also have another concept that we haven't covered yet, and that is Subjects, which is both an `Observable` and `Observer`. This is useful in many ways for example having a custom event emitter where you can expose the `subscribe` to consumers while you push data via the `next` method calls. There are a number of subjects that you can use, depending on the behavior that you want. We'll first look at the plain `Subject`.
12 |
13 | ```typescript
14 | import { Subject } from 'rxjs';
15 |
16 | const event$ = new Subject();
17 |
18 | event$.next('hello');
19 |
20 | const subscription = event$.subscribe({
21 | next: val => console.log(`Received ${val}`)
22 | });
23 |
24 | event$.next('world');
25 | // Received: world
26 | ```
27 |
28 | ## Going Hot and Cold
29 |
30 | As you'll notice from the above code, we're missing the 'hello' value that we emitted because we had not yet subscribed to the `Subject`. This style of `Observable` or rather `Subject` is what we call a "Hot Observable", meaning it emits data whether we are listening to it or not, such as events like mouse moves, and in this case `Subject`. In contrast, a "Cold Observable" is one that will always emit the same data no matter when you subscribe, such as operators like `from`, `of`, and others.
31 |
32 | ## Replaying your data with ReplaySubject
33 |
34 | We have ways around our problematic `Subject` with data loss due to subscribing late. One way is using the `ReplaySubject`. This does exactly what you think it might, it replays data, given the criteria of a given time window or number of items. For example, I can specify that I only want to replay items from the past five minutes, or 500 items in the queue. By default, the `ReplaySubject` will retain data indefinitely, so that any subscriber can get the same data no matter when they subscribe.
35 |
36 | ```typescript
37 | import { ReplaySubject } from 'rxjs';
38 |
39 | // Replays all data
40 | const replayAll = new ReplaySubject();
41 |
42 | // Replays only the past 10 items
43 | const replayCount = new ReplaySubject(10);
44 |
45 | // Replays on the past 5 minutes
46 | const replayTime = new ReplaySubject(Number.POSITIVE_INFINITY, 5 * 1000 * 60);
47 |
48 | // Replays either on count or time
49 | const replayTime = new ReplaySubject(50, 5 * 1000 * 60);
50 | ```
51 |
52 | So, now that we have this in place, we could easily capture the event we missed and replay it back, so no more chances of lost data!
53 |
54 | ```typescript
55 | import { ReplaySubject } from 'rxjs';
56 |
57 | const event$ = new ReplaySubject();
58 |
59 | event$.next('hello');
60 |
61 | const subscription = event$.subscribe({
62 | next: val => console.log(`Received ${val}`)
63 | });
64 |
65 | event$.next('world');
66 | // Received: hello
67 | // Received: world
68 | ```
69 |
70 | Much better results and we're not missing any data. What we have to be careful of here, however, is the size of our `ReplaySubject` buffer since we're keeping all those values by default.
71 |
72 | ## Caching Data with AsyncSubject
73 |
74 | Another style of subject is where we'll receive the result of some async computation, such as a `Promise`. The `AsyncSubject` is a mirror for the JavaScript `Promise` as it will only resolve the data once, and then give the same value to all incoming subscribers. You'll note that in order to finish the async computation, the `complete` must be called explicitly, and then the data is cached.
75 |
76 | ```typescript
77 | import { AsyncSubject } from 'rxjs';
78 |
79 | const result$ = new AsyncSubject();
80 |
81 | result$.subscribe({
82 | next: val => console.log(`First subscriber: ${val}`)
83 | });
84 |
85 | result$.next('hello world');
86 | result$.complete();
87 |
88 | result$.subscribe({
89 | next: val => console.log(`Second subscriber: ${val}`)
90 | });
91 | // First subscriber: hello world
92 | // Second subscriber: hello world
93 | ```
94 |
95 | You could hypothetically keep emitting values and then finally call `complete` and only the last value will be kept.
96 |
97 | ```typescript
98 | import { AsyncSubject } from 'rxjs';
99 |
100 | const result$ = new AsyncSubject();
101 |
102 | result$.subscribe({
103 | next: val => console.log(`First subscriber: ${val}`)
104 | });
105 |
106 | result$.next('hello world');
107 | result$.next('goodbye world');
108 | result$.complete();
109 |
110 | result$.subscribe({
111 | next: val => console.log(`Second subscriber: ${val}`)
112 | });
113 | // First subscriber: goodbye world
114 | // Second subscriber: goodbye world
115 | ```
116 |
117 | There are many uses for the `AsyncSubject` where it caches the data. There are downsides of this one of course as you can't retry a failed `AsyncSubject` as you can with a normal `Observable`. That's for another discussion on error handling.
118 |
119 | ## State Machines with BehaviorSubject
120 |
121 | The last subject is one of the most used types of subjects, called the `BehaviorSubject`. This subject takes an initial value, such as the initial state of a state machine, such as a shopping cart or whatever you're trying to model.
122 |
123 | ```typescript
124 | import { BehaviorSubject } from 'rxjs';
125 |
126 | const event$ = new BehaviorSubject('initial value');
127 |
128 | event$.subscribe({
129 | next: val => console.log(`Data: ${val}`)
130 | });
131 |
132 | event$.next('hello world');
133 | event$.next('goodbye world');
134 |
135 | // Data: initial value
136 | // Data: hello world
137 | // Data: goodbye world
138 | ```
139 |
140 | What makes the `BehaviorSubject` even more interesting is that you can peek at its current value via the `getValue()` method call.
141 |
142 | ```typescript
143 | import { BehaviorSubject } from 'rxjs';
144 |
145 | const event$ = new BehaviorSubject('initial value');
146 |
147 | console.log(`Current value: ${event$.getValue()}`);
148 |
149 | event$.subscribe({
150 | next: val => console.log(`Data: ${val}`)
151 | });
152 |
153 | event$.next('hello world');
154 |
155 | console.log(`Current value: ${event$.getValue()}`);
156 |
157 | event$.next('goodbye world');
158 |
159 | console.log(`Current value: ${event$.getValue()}`);
160 | // Current value: initial value
161 | // Data: initial value
162 | // Data: hello world
163 | // Current value: hello world
164 | // Data: goodbye world
165 | // Current value: goodbye world
166 | ```
167 |
168 | I hope this gives you a clear understanding of Subjects and why you might use each. This will serve as a basis for creating a Redux clone going forward, stay tuned!
--------------------------------------------------------------------------------
/20/readme.md:
--------------------------------------------------------------------------------
1 | # Day 20 - Skip and Take Observables
2 |
3 | In the [previous entry](../19/readme.md), we talked about throttling and debouncing our Observables. Today, we're going to go back to more fundamentals with skipping and taking values from Observables. This is important as we think of Observables over time, the need to skip values is important, just as it is to truncate our Observables via taking.
4 |
5 | ## Taking a number of values with take
6 |
7 | The first section we will look at is taking a number of values from our Obesrvable with the `take` operator. This is extra useful if you have an infinite stream and only want a certain number of values. We have shown this operator plenty in previous posts, but now it deserves a spot of its own. This operator simply takes a number of values you want from the Observable stream, and then completes the it.
8 |
9 | ```typescript
10 | import { interval } from 'rxjs';
11 | import { take } from 'rxjs/operators';
12 |
13 | const num$ = interval(500).pipe(
14 | take(3)
15 | );
16 |
17 | const subscription = num$.subscribe({
18 | next: item => console.log(`Next: ${item}`),
19 | complete: () => console.log('Done')
20 | });
21 | // Next: 0
22 | // Next: 1
23 | // Next: 2
24 | // Done
25 | ```
26 |
27 | As you can see, we get three values from our stream, followed by the completion, thus terminating what would have been an infinite stream.
28 |
29 | ## Taking the last values with takeLast
30 |
31 | What if we want to only take the last number of values from a stream, such as the last five values? To do that, we would use the `takeLast` operator. Unlike the previous operator, `take`, this operator requires our Observable sequence to be finite in size, for example using a `range` to create data and then pluck the last values from it.
32 |
33 | ```typescript
34 | import { range } from 'rxjs';
35 | import { takeLast } from 'rxjs/operators';
36 |
37 | const num$ = range(0, 10).pipe(
38 | takeLast(3)
39 | );
40 |
41 | const subscription = num$.subscribe({
42 | next: item => console.log(`Next: ${item}`),
43 | complete: () => console.log('Done')
44 | });
45 | // Next: 7
46 | // Next: 8
47 | // Next: 9
48 | // Done
49 | ```
50 |
51 | ## Taking values while a predicate holds true with takeWhile
52 |
53 | Another variation on take is while a predicate holds true via the `takeWhile` operator. For example, we could take values less than a certain value. This differs from `filter` as the sequence completes once the `predicate` is no longer true. The predicate takes in both the current value as well as the index.
54 |
55 | ```typescript
56 | import { range } from 'rxjs';
57 | import { takeLast } from 'rxjs/operators';
58 |
59 | const num$ = range(0, 10).pipe(
60 | takeWhile((item, index) => index < 4)
61 | );
62 |
63 | const subscription = num$.subscribe({
64 | next: item => console.log(`Next: ${item}`),
65 | complete: () => console.log('Done')
66 | });
67 | // Next: 0
68 | // Next: 1
69 | // Next: 2
70 | // Next: 3
71 | // Done
72 | ```
73 |
74 | ## Taking values until another Observable with takeUntil
75 |
76 | One of the more interesting operators, especially dealing with infinite streams, is to truncate our current stream based upon another stream. This is via the `takeUntil` method which takes in another Observable and truncates when the other fires its first `next` value. One common scenario you see for this is drag and drop which could be described as starting with mouse down and mouse move, calculate the delta between the start and current position, taking until the mouse up happens.
77 |
78 | ```typescript
79 | import { fromEvent } from 'rxjs';
80 | import { map, mergeMap, takeUntil } from 'rxjs/operators';
81 |
82 | const el = document.querySelector('#drag');
83 | const mouseup$ = fromEvent(el, 'mouseup');
84 | const mousedown$ = fromEvent(el, 'mousedown');
85 | const mousemove$ = fromEvent(document, 'mousemove');
86 |
87 | const mousedrag$ = mousedown$.pipe(
88 | mergeMap(down => {
89 | let startX = down.offsetX, startY = down.offsetY;
90 |
91 | return mousemove$.pipe(
92 | map(move => {
93 | move.preventDefault();
94 |
95 | return {
96 | left: move.clientX - startX,
97 | top: move.clientY - startY
98 | };
99 | }),
100 | takeUntil(mouseup$)
101 | );
102 | })
103 | );
104 | ```
105 |
106 | This is a pretty useful operator outside of this particular scenario, but it demonstrates the capability of combining two streams together until another stream interrupts it.
107 |
108 | ## Skipping a number values with skip
109 |
110 | Just as we have a way of taking a number of values, we can also skip a number of values using the `skip` operator. What makes this more fun is combining it with `zip` to combine a stream to get the previous and current values.
111 |
112 | ```typescript
113 | import { range, zip } from 'rxjs';
114 | import { skip } from 'rxjs/operators';
115 |
116 | const num$ = range(0, 5);
117 | const pair$ = zip(num$, num$.pipe(skip(1)));
118 |
119 | const subscription = pair$.subscribe({
120 | next: item => console.log(`Next: ${item}`),
121 | complete: () => console.log('Done')
122 | });
123 | // Next: 0,1
124 | // Next: 1,2
125 | // Next: 2,3
126 | // Next: 3,4
127 | // Done
128 | ```
129 |
130 | ## Skipping the last number of values with skipLast
131 |
132 | Just as we can take a number of items from the last of the stream with `takeLast`, we can also skip those last values using `skipLast`. This is useful for trimming off data we don't want at the end.
133 |
134 | ```typescript
135 | import { range } from 'rxjs';
136 | import { skipLast } from 'rxjs/operators';
137 |
138 | const num$ = range(0, 10).pipe(skipLast(8));
139 |
140 | const subscription = num$.subscribe({
141 | next: item => console.log(`Next: ${item}`),
142 | complete: () => console.log('Done')
143 | });
144 | // Next: 0
145 | // Next: 1
146 | // Done
147 | ```
148 |
149 | ## Skipping data while the predicate is true with skipWhile
150 |
151 | As you'll notice a trend, if we have a take method, chances are we will have a corresponding skip method as well. This also holds true for `takeWhile` and `skipWhile` where we will skip values until the predicate no longer holds true. Much like before, the predicate takes the current value as well as the index and you return a boolean value whether the predicate holds or not.
152 |
153 | ```typescript
154 | import { range } from 'rxjs';
155 | import { skipWhile } from 'rxjs/operators';
156 |
157 | const num$ = range(0, 10).pipe(
158 | skipWhile((item, index) => index < 7)
159 | );
160 |
161 | const subscription = num$.subscribe({
162 | next: item => console.log(`Next: ${item}`),
163 | complete: () => console.log('Done')
164 | });
165 | // Next: 7
166 | // Next: 8
167 | // Next: 9
168 | // Done
169 | ```
170 |
171 | ## Skipping until another Observable fires with skipUntil
172 |
173 | Just like `takeUntil`, `skipUntil` allows us to skip values in our source Observable until the other Observable fires its first `next` value. This could be useful in scenarios when you only want to listen to data when a 'open' event happens and then listen to the data on the 'data' event for example.
174 |
175 | ```typescript
176 | import { EventEmitter } from 'events';
177 | import { fromEvent } from 'rxjs';
178 | import { skipUntil, takeUntil } from 'rxjs/operators';
179 |
180 | const emitter = new EventEmitter();
181 |
182 | const open$ = fromEvent(emitter, 'open');
183 | const close$ = fromEvent(emitter, 'close');
184 | const data$ = fromEvent(emitter, 'data');
185 |
186 | const value$ = data$.pipe(
187 | skipUntil(open$),
188 | takeUntil(close$)
189 | );
190 | ```
191 |
192 | This gives you a brief overview of what is the art of the possible with skipping and taking data from Observable streams. Stay tuned for more!
--------------------------------------------------------------------------------
/21/readme.md:
--------------------------------------------------------------------------------
1 | # Day 21 - Error Handling with Observables
2 |
3 | In the [previous entry](../20/readme.md), we covered some basic operators for skipping and taking values based upon certain criteria. Today, we're going to go back to a fundamental concept of error handling.
4 |
5 | ## Sometimes errors happen
6 |
7 | One of the key concepts of the Reactive Extensions is its completion semantics. What we're trying to accomplish is a push based version of the basic iteration loop with try/catch. This matches perfectly with our `Observer` which has a `next`, with either an `complete` or an `error` terminating the sequence. Here, I've mapped these methods to `handle` methods so you understand where each fits in the world.
8 |
9 | ```typescript
10 | try {
11 | for (let item of source) {
12 | handleNext(item);
13 | }
14 |
15 | handleComplete();
16 | } catch (err) {
17 | handleError(err);
18 | }
19 | ```
20 |
21 | When we subscribe to a given Observable, we have the option of supplying an `error` handler to our `Observer` in case an error happens. If we do not supply an error handler, a general error will be thrown and script halted. This is different from Promises where they will report unhandled rejections via the 'unhandledRejection' on the host.
22 |
23 | ```typescript
24 | import { of } from 'rxjs';
25 | import { map } from 'rxjs/operators';
26 |
27 | const num$ = of(1, 2, 3).pipe(
28 | map(() => { throw new Error('woops'); })
29 | );
30 |
31 | num$.subscribe({
32 | next: item => console.log(`Next: ${item}`)
33 | });
34 | ```
35 |
36 | If we run this, our process will crash such as the following which will tell us where the error occurred as well as the stack trace.
37 |
38 | ```bash
39 | $ npx ts-node 21/index.ts
40 | rxjs-advent-2018/21/index.ts:5
41 | map(() => { throw new Error('woops'); })
42 | ^
43 | Error: woops
44 | ```
45 |
46 | We can get around this by adding an error handler with the subscription where we will display the error's message.
47 |
48 | ```typescript
49 | import { of } from 'rxjs';
50 | import { map } from 'rxjs/operators';
51 |
52 | const num$ = of(1, 2, 3).pipe(
53 | map(() => { throw new Error('woops'); })
54 | );
55 |
56 | num$.subscribe({
57 | next: item => console.log(`Next: ${item}`),
58 | error: err => console.log(`Error: ${err.message}`)
59 | });
60 | // Error: woops
61 | ```
62 |
63 | Keep in mind, that errors could happen at any time, and not just the first element which we're doing right now.
64 |
65 | ```typescript
66 | import { of } from 'rxjs';
67 | import { map } from 'rxjs/operators';
68 |
69 | const num$ = of(1, 2, 3).pipe(
70 | map(item => {
71 | if (item > 2) {
72 | throw new Error('woops');
73 | } else {
74 | return item;
75 | }
76 | })
77 | );
78 |
79 | num$.subscribe({
80 | next: item => console.log(`Next: ${item}`),
81 | error: err => console.log(`Error: ${err.message}`)
82 | });
83 | // Next: 1
84 | // Next: 2
85 | // Error: woops
86 | ```
87 |
88 | ## Throwing errors
89 |
90 | Just as we randomly threw errors in the previous examples, we could also create errors via the `throwError` factory operation. This is useful for modeling errors when an Observable is required. Not only that, but we can control it via a Scheduler if need be.
91 |
92 | ```typescript
93 | import { throwError } from 'rxjs';
94 |
95 | const num$ = throwError(new Error('woops'));
96 |
97 | num$.subscribe({
98 | next: item => console.log(`Next: ${item}`),
99 | error: err => console.log(`Error: ${err.message}`)
100 | });
101 | // Error: woops
102 | ```
103 |
104 | ## Catching errors
105 |
106 | Just as we have the ability to throw errors via the `throwError` or just throwing errors, we can also recover from our errors via the `catchError` operator. This allows us to inspect the current error, and return an Observable which continues the sequence. In this case, we'll throw an error once we get past our second item and then merge in an error. I will also include a console logging mechanism to show that an error has been thrown and caught.
107 |
108 | ```typescript
109 | import { of, throwError } from 'rxjs';
110 | import { catchError, mergeMap } from 'rxjs';
111 |
112 | const num$ = of(1, 2, 3).pipe(
113 | mergeMap(item => {
114 | if (item > 2) {
115 | return throwError(new Error('woops'));
116 | }
117 |
118 | return of(item * item);
119 | }),
120 | catchError(err => {
121 | console.log(`Error caught: ${err.message}`);
122 | return of(42);
123 | })
124 | );
125 |
126 | num$.subscribe({
127 | next: item => console.log(`Next: ${item}`),
128 | error: err => console.log(`Error: ${err.message}`)
129 | });
130 | // Next: 1
131 | // Next: 4
132 | // Error caught: woops
133 | // Next: 42
134 | ```
135 |
136 | As you can see, our handler caught the error and then we returned a sequence of a single item of `42`.
137 |
138 | ## Resuming after an error VB Style
139 |
140 | Just as we can catch an error via the `catchError` operator, we can also go Visual Basic Style with `onErrorResumeNext`, which is an ode to the Visual Basic [`On Error Resume Next`](https://docs.microsoft.com/en-us/dotnet/visual-basic/language-reference/statements/on-error-statement#on-error-resume-next), of which the Reactive Extensions creator, Erik Meijer, is a fan. This allows us to continue after an error with a new Observable or set of Observables. This will keep on going even if the last operation succeeds.
141 |
142 | ```typescript
143 | import { of, throwError } from 'rxjs';
144 | import { onErrorResumeNext } from 'rxjs/operators';
145 |
146 | const num1$ = throwError(new Error('first'));
147 | const num2$ = throwError(new Error('second'));
148 | const num3$ = of(42);
149 | const num4$ = of(56);
150 |
151 | const num$ = num1$.pipe(
152 | onErrorResumeNext(num2$, num3$)
153 | );
154 |
155 | num$.subscribe({
156 | next: item => console.log(`Next: ${item}`),
157 | error: err => console.log(`Error: ${err.message}`)
158 | });
159 | // Next: 42
160 | // Next: 56
161 | ```
162 |
163 | ## Finalizing an operation
164 |
165 | So far we've covered the `try/catch` aspect of Observables, but not the `finally` part just yet. We have the ability to, at the end of the sequence, we can invoke some action. This is useful if we have some resources open and we want to ensure that they are closed, regardless of whether an error has occurred or not.
166 |
167 | ```typescript
168 | import { of, throwError } from 'rxjs';
169 | import { finalize } from 'rxjs/operators';
170 |
171 | of(42).pipe(
172 | finalize(() => console.log('Finally called'))
173 | ).subscribe({
174 | next: item => console.log(`Next: ${item}`),
175 | error: err => console.log(`Error: ${err.message}`),
176 | complete: () => console.log('Done')
177 | });
178 | // Next: 42
179 | // Done
180 | // Finally called
181 |
182 | throwError(new Error('woops')).pipe(
183 | finalize(() => console.log('Finally called'))
184 | ).subscribe({
185 | next: item => console.log(`Next: ${item}`),
186 | error: err => console.log(`Error: ${err.message}`),
187 | complete: () => console.log('Done')
188 | });
189 | // Error: woops
190 | // Finally called
191 | ```
192 |
193 | As you'll notice, the `finalize` operation happens after the `complete` handler is called.
194 |
195 | ## Retrying an operation
196 |
197 | Operations can fail such as with an HTTP request or some other network connection issue, so we need a way to retry operations, such as try three times and then give up if it doesn't work. We accomodate this feature via the `retry` operator which specifies the number of retries before the operation gives up and thus letting the error through. Note if you do not specify a retry count, then it will retry indefinitely.
198 |
199 | ```typescript
200 | import { Observable } from 'rxjs';
201 | import { retry } from 'rxjs/operators';
202 |
203 | let retryCount = 0;
204 | const obs$ = new Observable(observer => {
205 | if (++retryCount == 2) {
206 | observer.next(42);
207 | observer.complete();
208 | } else {
209 | observer.error(new Error('woops'));
210 | }
211 | });
212 |
213 | const nums$ = obs$.pipe(retry(3));
214 |
215 | num$.subscribe({
216 | next: item => console.log(`Next: ${item}`),
217 | error: err => console.log(`Error: ${err.message}`)
218 | });
219 | // Next: 42
220 | ```
221 |
222 | ## Conditionally retrying an operation
223 |
224 | The `retry` operator is good for many things like immediately retrying an operation. There may be instances, however, when you need finer grained control over when and how a retry happens. For that, we have the `retryWhen` operator which allows us to create a backoff strategy, for example, using `delayWhen` to specify the delayed time for that next try.
225 |
226 | ```typescript
227 | import { Observable, range, timer, zip } from 'rxjs';
228 | import { delayWhen, map, retryWhen, tap } from 'rxjs/operators';
229 |
230 | let retryCount = 0;
231 | const obs$ = new Observable(observer => {
232 | if (++retryCount == 3) {
233 | observer.next(42);
234 | observer.complete();
235 | } else {
236 | observer.error(new Error('woops'));
237 | }
238 | });
239 |
240 | const num$ = obs$.pipe(
241 | retryWhen(errors => {
242 | return zip(errors, range(1, 3)).pipe(
243 | map(([_, index]) => index),
244 | tap(item => console.log(`Retrying after ${item} seconds`)),
245 | delayWhen(item => timer(item * 1000))
246 | );
247 | })
248 | );
249 |
250 | num$.subscribe({
251 | next: item => console.log(`Next: ${item}`),
252 | error: err => console.log(`Error: ${err.message}`)
253 | });
254 | // Retrying after 1 seconds
255 | // Retrying after 2 seconds
256 | // Next: 42
257 | ```
258 |
259 | There are more advanced scenarios for error handling, but this should give you a flavor of what the art of the possible is when it comes to handling errors in your application!
--------------------------------------------------------------------------------
/08/readme.md:
--------------------------------------------------------------------------------
1 | # Day 8 - Mapping, Plucking, Tapping, and Filtering
2 |
3 | In the [previous entry](../07/readme.md), we covered how RxJS has evolved from using dot chaining to "lettable" operators, and finally how we ended up with `pipe` to chain our operators together. Today, we're going to cover probably the two most used operators, `map` and `filter`. One of the early goals of RxJS was to make it as easy as possible to learn, so if you knew the [Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) methods such as [`Array.prototype.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map), [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter), [`Array.prototype.reduce`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce), they operate pretty much the same way as Array does, except for being a pull collection, it becomes a push collection with values over time.
4 |
5 | ## Mapping one value to another
6 |
7 | One of the most common operations over data structure is [`map`](https://en.wikipedia.org/wiki/Map_%28higher-order_function%29) which applies to each element of a [functor](https://en.wikipedia.org/wiki/Functor), such as arrays, and returns a new instance with results in the same order. In simple terms, a functor is just anything that supports that mapping operation such as an array, a Promise, and yes even an Observable. In JavaScript, we have [`Array.prototype.map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) which creates a new array with the projected values. In RxJS, we give you the operator `map` in the `rxjs/operators` imports.
8 |
9 | As we showed in the previous post, implementing `map` in JavaScript is pretty simple, for example we could have implemented `map` ourselves on an Array. This creates a new array, filling the array with a function call on each element from the source array, and returning the new array.
10 |
11 | ```typescript
12 | function map(source: Array, selector: (value: T, index: number) => R, thisArg?: any): Array {
13 | const length = source.length;
14 | const results = new Array(length);
15 | for (let i = 0; i < length; i++) {
16 | results[i] = selector.call(thisArg, source[i], i)
17 | }
18 |
19 | return results;
20 | }
21 |
22 | map([1, 2, 3], x => x * x);
23 | // [1, 4, 9]
24 | ```
25 |
26 | With the advent of Iterables in ES2015, we could generalize this a bit more to apply for both `Set` and `Array` and have it lazy as well. We can iterate over the existing structure using the [`for .. of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) and then yielding the transformed item.
27 |
28 | ```typescript
29 | function* map(source: Iterable, selector: (value: T, index: number) => R, thisArg?: any): Iterable {
30 | let i = 0;
31 | for (let item of source) {
32 | yield selector.call(thisArg, item, i++);
33 | }
34 | }
35 |
36 | const mapped = map(new Set([1, 2, 3]), x => x * x);
37 | for (let item of mapped) {
38 | console.log(`Next: ${item}`);
39 | }
40 | // [1, 4, 9]
41 | ```
42 |
43 | Implementing this in Observables is almost as straightforward, except that we have to take care of the error case should our selector function throw an error, and forwarding the error and completion channels on through.
44 |
45 | ```typescript
46 | function map(selector: (value: T, index: number, thisArg?: any) => R) {
47 | return function mapOperator(source: Observable) : Observable {
48 | return new Observable(observer => {
49 | let index = 0;
50 | return source.subscribe({
51 | next: x => {
52 | let value: any;
53 | try {
54 | value = selector.call(thisArg, x, index++);
55 | } catch (err) {
56 | observer.error(err);
57 | return;
58 | }
59 |
60 | observer.next(value);
61 | }
62 | },
63 | error: err => observer.error(err),
64 | complete: () => observer.complete()
65 | });
66 | };
67 | }
68 | ```
69 |
70 | Luckily we have it nice and optimized in RxJS, so you can use it by simply importing it from `rxjs/operators`.
71 |
72 | ```typescript
73 | include { of } from 'rxjs';
74 | include { map } from 'rxjs/operators';
75 |
76 | const obs$ = of(1, 2, 3).pipe(
77 | map(x => x * x)
78 | );
79 |
80 | obs$.subscribe({
81 | next: x => console.log(`Next: ${x}`)
82 | });
83 |
84 | // Next: 1
85 | // Next: 4
86 | // Next: 9
87 | ```
88 |
89 | ## Plucking Data
90 |
91 | With the `map` operator, we can easily project values to a new sequence. But, what if we wanted to just pull out values from the sequence itself? To do that, we have the `pluck` operator, which allows us to specify which properties to pull out. As you will notice, we can specify multiple values which recursively walks the object to pluck that desired value.
92 |
93 | ```typescript
94 | import { from } from 'rxjs';
95 | import { pluck } from 'rxjs/operators';
96 |
97 | const people = [
98 | { name: 'Kim' },
99 | { name: 'Bob' },
100 | { name: 'Joe' }
101 | ];
102 |
103 | const person$ = from(people).pipe(pluck('name'));
104 |
105 | const props = [
106 | { prop1: { prop2: 'Kim' } },
107 | { prop1: { prop2: 'Bob' } },
108 | { prop1: { prop2: 'Joe' } }
109 | ];
110 |
111 | const data$ = from(data).pipe(pluck('prop1', 'prop2'));
112 | ```
113 |
114 | ## Tapping Data
115 |
116 | While `map` allows us to project a value to a new sequence. But, what if we want to cause a side effect for each item, while project the current value to a new sequence? That's what the `tap` operator is for, which allows us to intercept not only `next` calls, but also `error` and `complete` calls as well. This is good for when during a sequence, some side effect needs to happen, for example a progress status to be updated, while not affecting the stream itself.
117 |
118 | ```typescript
119 | import { of } from 'rxjs';
120 | import { tap } from 'rxjs/operators';
121 |
122 | const val$ = of(1, 2, 3).pipe(
123 | tap({
124 | next: item => console.log(`Tapped next: ${item}`),
125 | complete: () => console.log('Tapped complete')
126 | })
127 | );
128 |
129 | const subscription = val$.subscribe({
130 | next: item => console.log(`Next: ${item}`),
131 | complete: () => console.log('Done')
132 | });
133 | // Tapped next: 1
134 | // Next: 1
135 | // Tapped next: 2
136 | // Next: 2
137 | // Tapped next: 3
138 | // Next: 3
139 | // Tapped complete
140 | // Done
141 | ```
142 |
143 | ## Filtering Data
144 |
145 | Another higher-order function that's often used is [`filter`](https://en.wikipedia.org/wiki/Filter_%28higher-order_function%29), which iterates over a given data structure, and creates a new data structure where the predicate returns true. No magical functional programming jargon like functor required for this operator! In JavaScript, we have it implemented for us on the Array with [`Array.prototype.filter`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter).
146 |
147 | We could easily implement this ourselves much like we did for `map` above, iterating over the array and only including values where the predicate evaluates to true.
148 |
149 | ```typescript
150 | function filter(source: Array, predicate: (value: T, index: number) => boolean, thisArg?: any) {
151 | let results = [];
152 | for (let i = 0; i < source.length; i++) {
153 | if (predicate.call(thisArg, source[i], i)) {
154 | results.push(source[i]);
155 | }
156 | }
157 |
158 | return results;
159 | }
160 |
161 | filter([1, 2, 3], x => x % 2 === 0);
162 | // [2]
163 | ```
164 |
165 | Similarly, as with the above, we can implement `filter` on things that implement `[Symbol.iterator]` such as Array, Map, Set, and even generator functions.
166 |
167 | ```typescript
168 | let i = 0;
169 | for (let item of source) {
170 | if (predicate.call(thisArg, item, i++)) {
171 | yield item;
172 | }
173 | }
174 | }
175 |
176 | const filtered = filter(new Set([1, 2, 3]), x => x % 2 === 0);
177 | for (let item of filtered) {
178 | console.log(`Next: ${item}`);
179 | }
180 | // [2]
181 | ```
182 |
183 | Lastly, implementing this for Observables is pretty much as straightforward, sending values to `observer.next` only if the predicate returns true.
184 |
185 | ```typescript
186 | function filter(predicate: predicate: (value: T, index: number) => boolean, thisArg?: any) {
187 | return function filterOperator(source: Observable): Observable {
188 | return new Observable(observer => {
189 | let i = 0;
190 | return source.subscribe({
191 | next: x => {
192 | function* filter(source: Iterable, predicate: (value: T, index: number) => bool, thisArg?: any) {
193 | let shouldYield = false;
194 | try {
195 | shouldYield = predicate.call(thisArg, x, i++);
196 | } catch (err) {
197 | observer.error(err);
198 | return;
199 | }
200 |
201 | if (shouldYield) {
202 | observer.next(x);
203 | }
204 | },
205 | error: err => observer.error(err),
206 | complete: () => observer.complete()
207 | });
208 | });
209 | }
210 | }
211 | ```
212 |
213 | Luckily, just as before, we don't need to implement this ourselves, and instead it is provided by the `filter` operator in `rxjs/operators`.
214 |
215 | ```typescript
216 | import { of } from 'rxjs';
217 | import { filter } from 'rxjs/operators';
218 |
219 | const obs$ = of(1, 2, 3).pipe(
220 | filter(x => x % 2 === 0),
221 | map(x => x * x)
222 | );
223 |
224 | obs$.subscribe({
225 | next: x => console.log(`Next: ${x}`)
226 | });
227 |
228 | // Next: 4
229 | ```
230 |
231 | That's enough for now as we've learned how to create operators from scratch with the two most basic ones, `map` and `filter`. I hope this also gives you the confidence that you too could write something like RxJS. In the next posts, we'll cover more of the everyday operators you'll be using as you get more comfortable with RxJS!
232 |
--------------------------------------------------------------------------------
/22/readme.md:
--------------------------------------------------------------------------------
1 | # Day 22 - You're Hot and You're Cold
2 |
3 | In the [previous entry](../21/readme.md), we covered pretty much an extensive look into error handling. Today, we're going to go off on a different tangent and revisit [Day 11](../11/readme.md) which talked about Subjects, but this time coming at it from the perspective of hot and cold Observables. Let's go through some definitions here in this post.
4 |
5 | ## Cold Observables
6 |
7 | To be a cold Observable, it must have the following two properties:
8 |
9 | 1. The Observable does not emit values until a subscriber subscribes
10 | 2. If we have more than one subscriber, then the Observable will emit all values to all subscribers one by one.
11 |
12 | In other words, cold observables are unicast observables, one producer to one consumer. To illustrate this concept in action, let's look at some code.
13 |
14 | ```typescript
15 | import { Observable } from 'rxjs';
16 |
17 | const cold$ = new Observable(observer => {
18 | for (let item of [42, 56]) {
19 | console.log(`Observable next: ${item}`);
20 | observer.next(item);
21 | }
22 | });
23 |
24 | const subcription1 = cold$.subscribe({
25 | next: item => console.log(`Subscription 1: ${item}`)
26 | });
27 |
28 | const subcription2 = cold$.subscribe({
29 | next: item => console.log(`Subscription 2: ${item}`)
30 | });
31 | ```
32 |
33 | Running this through, we get the following which shows that subscription 1, gets its own values and then finished, followed by subscription 2 with its values. You'll notice that each time a new subscriber comes in, they get a fresh set of data.
34 |
35 | ```bash
36 | $ npx ts-node 22/index.ts
37 | Observable next: 42
38 | Subscription 1: 42
39 | Observable next: 56
40 | Subscription 1: 56
41 | Observable next: 42
42 | Subscription 2: 42
43 | Observable next: 56
44 | Subscription 2: 56
45 | ```
46 |
47 | What if we wanted to share the data instead of having to produce it again and again? In that case, we have a mechanism or rather several to turn it on its head to make it a "Hot Observable"
48 |
49 | ## Hot Observables
50 |
51 | Let's contrast this with Hot Observables, or call it multicasted Observables, where it has the following properties:
52 |
53 | 1. Observables do not wait for any subscription, as the data starts emitting when it was created.
54 | 2. They don't emit the sequence of items again for any new subscriber.
55 | 3. When a new item is emitted by a hot observable, all subscribed observers will get the emitted item all at once.
56 |
57 | We can show what a hot observable is by either creating a `Subject`, or another example would be using a `ConnectableObservable`. This `ConnectableObservable` allows us to take the multicasted observable and have our subscribers subscribe, and once they are done, we can then call `.connect()`. In this example, we will use `publish()`, which behind the scenes, is a variant of the `multicast` operator using a `Subject`, which we will be talking about later.
58 |
59 | ```typescript
60 | import { Observable, ConnectableObservable } from 'rxjs';
61 | import { publish } from 'rxjs/operators';
62 |
63 | const cold$ = new Observable(observer => {
64 | for (let item of [42, 56]) {
65 | console.log(`Observable next: ${item}`);
66 | observer.next(item);
67 | }
68 | });
69 |
70 | const hot$ = cold$.pipe(publish()) as ConnectableObservable;
71 |
72 | const subcription1 = hot$.subscribe({
73 | next: item => console.log(`Subscription 1: ${item}`)
74 | });
75 |
76 | const subcription2 = hot$.subscribe({
77 | next: item => console.log(`Subscription 2: ${item}`)
78 | });
79 |
80 | hot$.connect();
81 | ```
82 |
83 | If you leave off the `.connect()`, you won't see any data flowing through, and it's only when you call the method does the data start flowing to all recipients.
84 |
85 | ```bash
86 | $ npx ts-node 22/index.ts
87 | Observable next: 42
88 | Subscription 1: 42
89 | Subscription 2: 42
90 | Observable next: 56
91 | Subscription 1: 56
92 | Subscription 2: 56
93 | ```
94 |
95 | At the time of `.connect()`, even if you don't have any subscribers attached, the data will still flow as seen below with this example.
96 |
97 | ```typescript
98 | import { Observable, ConnectableObservable } from 'rxjs';
99 | import { publish } from 'rxjs/operators';
100 |
101 | const cold$ = new Observable(observer => {
102 | for (let item of [42, 56]) {
103 | console.log(`Observable next: ${item}`);
104 | observer.next(item);
105 | }
106 | });
107 |
108 | const hot$ = cold$.pipe(publish()) as ConnectableObservable;
109 | hot$.connect();
110 | ```
111 |
112 | Running this through will show that the data still flows despite nobody listening to it.
113 |
114 | ```bash
115 | $ npx ts-node 22/index.ts
116 | Observable next: 42
117 | Observable next: 56
118 | ```
119 |
120 | Our `ConnectableObservable` has another way of determining when subscriptions are made outside of the manual `connect()` method, amd that's via the `refCount` method. This method takes care of determining when subscriptions are made, maintaining a reference count of them, and cools the observable so it doesn't flow. When a subscription is made, the reference count is incremented. If the prior reference count was zero, then the underlying Subject is subscribed and data starts flowing, thus making the Observable hot again. And when the subscription is disposed, and the reference count drops to zero, the underlying Subject is unsubscribed from the source. This is the most used operator when it comes to hot observables, and is the default for such operators as `fromEvent`, `fromEventPattern` and others.
121 |
122 | So, why would we use hot observables? There are a number of reasons why you might want to use hot observables including:
123 |
124 | 1. When we want to run a job for its side effects and don't need a subscription
125 | 2. We want to broadcast our value to all subscribers at once.
126 | 3. When we don't want to trigger the source of data again for each new subscriber.
127 |
128 | The last one is probably the biggest point, for example, with events, not triggering adding yet another handler to the node for listening to the same event.
129 |
130 | ## Ways of Creating Hot Observables
131 |
132 | In RxJS, we provide two ways of creating hot observables.
133 |
134 | 1. Using the `ConnectableObservable` as described above.
135 | 2. Using a `Subject`, which we covered in [Day 11](../11/readme.md)
136 |
137 | I won't go over the whole idea of subjects here, instead, we'll talk more about `ConnectableObservable` and how you use it through the various `multicast` overloads.
138 |
139 | ### Looking at the publish operator
140 |
141 | The first way we'll talk about is using a `Subject` to backing store for our multicasted values using the `multicast` operator. This is done with the `publish()` operator which is just a convenience method for `multicast(() => new Subject())`. We've already covered `publish` above so I won't go into much more detail here.
142 |
143 | There is an overload to the `publish` method that is worth exploring. This turns the existing Observable hot, and then you have access to it inside the selector. You don't need to worry about ref counting or connecting, as it is all contained during that scope. Let's go through a quick example of that.
144 |
145 | ```typescript
146 | import { interval, merge, range, zip } from 'rxjs';
147 | import { map, publish } from 'rxjs/operators';
148 |
149 | const hot$ =
150 | zip(
151 | interval(1000),
152 | range(0, 2)
153 | )
154 | .pipe(publish(multicasted$ => {
155 | return merge(
156 | multicasted$.pipe(map(x => `stream1: ${x}`)),
157 | multicasted$.pipe(map(x => `stream2: ${x}`)),
158 | multicasted$.pipe(map(x => `stream3: ${x}`))
159 | );
160 | }));
161 |
162 | const subcription1 = hot$.subscribe({
163 | next: item => console.log(`Subscription 1: ${item}`)
164 | });
165 |
166 | const subcription2 = hot$.subscribe({
167 | next: item => console.log(`Subscription 2: ${item}`)
168 | });
169 | ```
170 |
171 | Running this latest code, we get the following results where we merged the inner results without having to do any `connect` or `refCount`.
172 |
173 | ```bash
174 | $ npx ts-node 22/index.ts
175 | Subscription 1: stream1: 0,0
176 | Subscription 1: stream2: 0,0
177 | Subscription 1: stream3: 0,0
178 | Subscription 2: stream1: 0,0
179 | Subscription 2: stream2: 0,0
180 | Subscription 2: stream3: 0,0
181 | Subscription 1: stream1: 1,1
182 | Subscription 1: stream2: 1,1
183 | Subscription 1: stream3: 1,1
184 | Subscription 2: stream1: 1,1
185 | Subscription 2: stream2: 1,1
186 | Subscription 2: stream3: 1,1
187 | ```
188 |
189 | ### Looking at the publishBehavior operator
190 |
191 | Another overload of the `multicast` operator is using a `BehaviorSubject` as the underlying subject is called oddly enough, `publishBehavior`. This contrasts with the earllier `publish` which uses the `Subject`. How this differs is that we supply an initial value to the Observable which is then emitted to the subscribers before any `next` call to the source Observable.
192 |
193 | Let's take a look at an example, where we supply two subscribers and then call `connect`, and then we add a third subscription which should then miss the initial default value.
194 |
195 | ```typescript
196 | import { Observable, ConnectableObservable } from 'rxjs';
197 | import { publishBehavior } from 'rxjs/operators';
198 |
199 | const cold$ = new Observable(observer => {
200 | for (let item of [42, 56]) {
201 | console.log(`Observable next: ${item}`);
202 | observer.next(item);
203 | }
204 | });
205 |
206 | const hot$ = cold$.pipe(publishBehavior(-1)) as ConnectableObservable;
207 |
208 | const subcription1 = hot$.subscribe({
209 | next: item => console.log(`Subscription 1: ${item}`)
210 | });
211 |
212 | const subcription2 = hot$.subscribe({
213 | next: item => console.log(`Subscription 2: ${item}`)
214 | });
215 |
216 | hot$.connect();
217 |
218 | const subcription3 = hot$.subscribe({
219 | next: item => console.log(`Subscription 3: ${item}`)
220 | });
221 | ```
222 |
223 | Running this example, we will indeed see the third subscription will miss the initial value of `-1` and will continue to receive the other values.
224 |
225 | ```bash
226 | $ npx ts-node 22/index.ts
227 | Subscription 1: -1
228 | Subscription 2: -1
229 | Observable next: 42
230 | Subscription 1: 42
231 | Subscription 2: 42
232 | Observable next: 56
233 | Subscription 1: 56
234 | Subscription 2: 56
235 | Subscription 3: 56
236 | ```
237 |
238 | ### Looking at the publishReplay operator
239 |
240 | Another version of the publish operator uses the `ReplaySubject` where we can replay values either by the count, or by the timeframe with the `publishReplay` operator. We'll demonstrate this operator by modifying the above sample, but replaying only the first value. In this case once again, since the third subscription happens after the `connect()` call, therefore it misses the first value.
241 |
242 | ```typescript
243 | import { Observable, ConnectableObservable } from 'rxjs';
244 | import { publishReplay } from 'rxjs/operators';
245 |
246 | const cold$ = new Observable(observer => {
247 | for (let item of [42, 56]) {
248 | console.log(`Observable next: ${item}`);
249 | observer.next(item);
250 | }
251 | });
252 |
253 | const hot$ = cold$.pipe(publishReplay(1)) as ConnectableObservable;
254 |
255 | const subcription1 = hot$.subscribe({
256 | next: item => console.log(`Subscription 1: ${item}`)
257 | });
258 |
259 | const subcription2 = hot$.subscribe({
260 | next: item => console.log(`Subscription 2: ${item}`)
261 | });
262 |
263 | hot$.connect();
264 |
265 | const subcription3 = hot$.subscribe({
266 | next: item => console.log(`Subscription 3: ${item}`)
267 | });
268 | ```
269 |
270 | Running this sample, we see the following output which shows that the third subscription misses the first value.
271 |
272 | ```bash
273 | $ npx ts-node 22/index.ts
274 | Observable next: 42
275 | Subscription 1: 42
276 | Subscription 2: 42
277 | Observable next: 56
278 | Subscription 1: 56
279 | Subscription 2: 56
280 | Subscription 3: 56
281 | ```
282 |
283 | Changing the above call to `publishReplay(2)`, we'll then see that the first value was preserved for the third subscription, however, it is different in that the third subscription will be catching up with getting the first value followed immediately by the second value.
284 |
285 | ```bash
286 | $ npx ts-node 22/index.ts
287 | Observable next: 42
288 | Subscription 1: 42
289 | Subscription 2: 42
290 | Observable next: 56
291 | Subscription 1: 56
292 | Subscription 2: 56
293 | Subscription 3: 42
294 | Subscription 3: 56
295 | ```
296 |
297 | ### Looking at the publishLast operator
298 |
299 | Finally, let's look at the last variation of the publish operator, this time wrapping the `AsyncSubject`. As we know from before, we don't actually get any values from our Observable until the `complete` has been fired.
300 |
301 | ```typescript
302 | import { Observable, ConnectableObservable } from 'rxjs';
303 | import { publishLast } from 'rxjs/operators';
304 |
305 | const cold$ = new Observable(observer => {
306 | for (let item of [42, 56]) {
307 | console.log(`Observable next: ${item}`);
308 | observer.next(item);
309 | }
310 |
311 | observer.complete();
312 | });
313 |
314 | const hot$ = cold$.pipe(publishLast()) as ConnectableObservable;
315 |
316 | const subcription1 = hot$.subscribe({
317 | next: item => console.log(`Subscription 1: ${item}`)
318 | });
319 |
320 | const subcription2 = hot$.subscribe({
321 | next: item => console.log(`Subscription 2: ${item}`)
322 | });
323 |
324 | hot$.connect();
325 |
326 | const subcription3 = hot$.subscribe({
327 | next: item => console.log(`Subscription 3: ${item}`)
328 | });
329 | ```
330 |
331 | Running this, we can see, this only fires the last value, and doesn't matter when you connect, you should all still get the same value.
332 |
333 | ```bash
334 | Observable next: 42
335 | Observable next: 56
336 | Subscription 1: 56
337 | Subscription 2: 56
338 | Subscription 3: 56
339 | ```
340 |
341 | ### Sharing is caring
342 |
343 | As I stated above, it's very common to use `publish` variants and `refCount` all at the same time. To make this easier, we have provided a number of `share*` methods which is shorthand for `publish*().refCount()` where the `*` represents the kind of publish we are doing as we did in the previous section.
344 |
345 | Stay tuned for yet more RxJS goodness as we wrap up this series!
--------------------------------------------------------------------------------