# Custom Operators

08.09.2019, GrillJS

## Chris Trześniewski

Senior Fronend Developer @Scalac

https://slides.com/ktrz/boost-exp-rx-custom-operators

## What's an Observable

• Stream
• Promise on steroids
• Function that tie observer to a producer

## What are operators?

Operators are the things you put in front of your stream before getting values out of it

~Ryan Chenkie

• filter
• debounce
• throttle

• map
• scan
• switchMap
• mergeMap

## Operators

• combineLatest
• withLatestFrom
• zip

• timer
• interval
• from / of
• create

## Operators

``````const source\$: Observable<number> = timer(1000);

const result\$: Observable<number> = source\$
.pipe(
filter(v => v % 3 === 1),
map(v => v * 7)
);``````

## Custom operators

• Creating specific operators based on existing ones
• Extract multiple operators
• more meaningful
• reusable
• Create missing pieces ourselves

## Under the hood

• Function
• Taking observable as an argument
• Returns observable
``````type OperatorFunction<T, R> =
(source: Observable<T>)): Observable<R>``````
``````const identity =
(source: Observable<any>) => source``````

## Custom operators

• static operators ie. notNull, isTruthy operator

## Extract most common use cases

``const notNull = filter(v => v !== null)``
``const isTruthy = filter(v => !!v)``

## Custom operators

• parametrised operators ie. `multiply`, `power`

## Extract most common use cases

``````const multiply =
m => map(v => v * m)``````
``````const power =
p => map(v => Math.pow(v, p))``````

## Group multiple operators into one

``````const myOperator =
<T,R>(source: Observable<T>) =>
source.pipe(operator1, operator2)``````

## Group multiple operators into one

``````const oddValueTimesSeven =
(num\$: Observable<number>) =>
num\$.pipe(
filter(v => v % 2 === 1),
map(v => v * 7),
)``````

## Group multiple operators into one

``````const extractData =
=> response\$.pipe(
isTruthy,
map(v => v.data),
)``````

## Create from scratch

``````const myOperator =
(source: Observable) =>
new Observable((observer: Subscriber) => {
source.subscribe({
next(x) {/* … */ observer.next(…)},
error(err) {/* … */ observer.error(…)},
complete(x) {/* … */ observer.complete(…)},
})
})``````

## Create from scratch

``````const customMap = <T, R>(
project: (value: T) => R
) => (source: Observable<T>) =>
new Observable<R>(observer =>
source.subscribe({
next(x) { observer.next(project(x)) },
error(err) { observer.error(err) },
complete() { observer.complete() }
})
)``````

## Create from scratch

``````const customMap = <T, R>(
project: (value: T) => R
): OperatorFunction<T, R> =>
(source: Observable<T>): Observable<R> => {
return source.lift(
new CustomMapOperator<T, R>(project)
);
};

class CustomMapOperator<T, R> implements Operator<T, R> {
constructor(private project: (value: T) => R) {}

call(sub: Subscriber<R>, src: any): TeardownLogic {
return src.subscribe(
new CustomMapSubscriber(sub, this.project)
);
}
}``````
``````/* ... */
(source: Observable<T>): Observable<R> => {
return source.lift(
new CustomMapOperator<T, R>(project)
);
};

class CustomMapOperator<T, R> implements Operator<T, R> {
constructor(private project: (value: T) => R) {}

call(subscriber: Subscriber<R>, source: any): TeardownLogic {
return source.subscribe(
new CustomMapSubscriber(subscriber, this.project)
);
}
}

class CustomMapSubscriber<T, R> extends Subscriber<T> {
constructor(
destination: Subscriber<R>,
private project: (value: T) => R
) {
super(destination);
}

protected _next(value: T): void {
this.destination.next(this.project(value));
}
}``````

## Create from scratch

• bufferDelay

• given a delay time parameter
• if more values are emitted in given time slot
they are spaced evenly with `delayTime`
• if value is emitted after specified delay time
it's emitted right away

## Summary

• Common use case ==> more descriptive name
• Chain of operations ==> one descriptive operator
• Our brand new custom operators
• Now we know how to harness the power of operators

## but remember...

• Don't overuse it

• Test it!