Observer &
Basic RxJS
Observer Pattern
The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.
Event Handling
function clickHandler(event) {
console.log('clicked');
}
document.addEventListener('click', clickHandler);
Proxy
function observe(o, callback) {
return new Proxy(o, {
set(target, property, value) {
callback(property, value);
target[property] = value;
},
});
}
const door = { open: false };
const doorObserver = observe(door, (property, value) => {
if (property === 'open') {
// ...
}
});
doorObserver.open = true;
React - useEffect
const [open, setOpen] = useState(false);
useEffect(() => {
function handler() {
setOpen(false);
}
document.addEventListener('click', handler);
return () => {
document.removeEventListener('click', handler);
};
}, [open]);
Simple Implementation


class Subject<P> {
constructor() {
this.observers = new Set<Observer<P>>();
}
subscribe(observer: Observer<P>) {
this.observers.add(observer);
}
unsubscribe(observer: Observer<P>) {
this.observers.remove(observer);
}
notify(payload: P) {
if (this.observers.size > 0) {
this.observers.forEach(observer => observer.next(payload));
}
}
}
class Observer<P> {
next(payload: P) {
// ...
}
}
So what ?
Even though we have subject and observers, but what can we do ?
Functional programming
const result = [1, 2, 3, 4]
.filter(num => num % 2 === 0)
.map(num => num * 2)
.flatMap(num => [num, num + 1]);
Iterator
function* getNumbers(words) {
for (let word of words) {
if (/^[0-9]+$/.test(word)) {
yield parseInt(word, 10);
}
}
}
const iterator = getNumbers('Rytass 尾牙 1/08 !');
iterator.next();
// { value: 1, done: false }
iterator.next();
// { value: 0, done: false }
iterator.next();
// { value: 8, done: false }
iterator.next();
// { value: undefined, done: true }
Description of Observer
It is mainly used to implement distributed event handling systems, in "event driven" software. In those systems, the subject is usually called a "stream of events" or "stream source of events", while the observers are called "sink of events".

RxJS
document.addEventListener('click', () => console.log('clicked!'));
import { fromEvent } from 'rxjs';
fromEvent(document, 'click')
.subscribe(() => console.log('Clicked!'));
Example
Debounced Resize
fromEvent(window, 'resize')
.pipe(debounceTime(250))
.subscribe(() => console.log('window resized!'));
Example
Button Clicked Times
fromEvent(document, 'click')
.pipe(
map(event => 1),
scan((total, now) => total + now, 0)
)
.subscribe(count => console.log(`Clicked ${count} times`));
Example
Counter
const plugBtn = document.querySelector('button.plus', 'click');
const minusBtn = document.querySelector('button.minus', 'click');
const plusClickEvent$ = fromEvent(plusBtn, 'click');
const minusClickEvent$ = fromEvent(minusBtn, 'click');
merge(plusClickEvent$.pipe(mapTo(1)), minusClickEvent$.pipe(mapTo(-1)))
.pipe(scan((total, now) => total + now, 0))
.subscribe(count => console.log(`Counter: ${count}`));
RxJS Operator
switchMap

RxJS Operator
switchMap
import { fromEvent, interval } from 'rxjs';
import { switchMap } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const result = clicks.pipe(switchMap((ev) => interval(1000)));
result.subscribe(x => console.log(x));
// click
// 0
// 1
// 2
// click
// 0
// 1
// 2
// 3
// 4
RxJS Operator
takeUntil

RxJS Operator
takeUntil
import { fromEvent, interval } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
const source = interval(1000);
const clicks = fromEvent(document, 'click');
const result = source.pipe(takeUntil(clicks));
result.subscribe(x => console.log(x));
Example
Autocomplete - fetch
const url =
"https://zh.wikipedia.org/w/api.php?action=opensearch&format=json&limit=5&origin=*";
const getKeywords = keyword =>
fetch(`${url}&search=${keyword}`, { method: "GET", mode: "cors" }).then(res =>
res.json()
);
Example
Autocomplete - keyword stream
const keyword$ = new Subject<string>();
keyword$
.pipe(
debounceTime(250),
switchMap(value =>
value
? from(getKeywords(value)).pipe(
map(res => res[1]),
takeUntil(cancel$)
)
: of([])
)
)
.subscribe(setKeywords);
onChange={event => keyword$.next(event.target.value)}
Example
Autocomplete - add cancel stream
const cancel$ = new Subject();
// ...
from(getKeywords(value)).pipe(
map(res => res[1]),
takeUntil(cancel$)
)
// ...
useEffect(() => () => cancel$.next(), []);

Observer & Basic RxJS
By jjaayy
Observer & Basic RxJS
- 440