Chris Trześniewski
Developer 🥑 Advocate
& Senior Fronend Developer @Scalac
Boost your
RxJS
experience with
Custom Operators
What's an Observable
Â
- Stream
- Promise on steroids
- Function that tie observer to a producer
Data delivered over time
Â
Operators
- filter
- debounce
- throttle
Filtering
Operators
- map
- scan (reduce)
- switchMap
- mergeMap
Transforming
Operators
- combineLatest
- withLatestFrom
- zip
Combining
Operators
- timer
- interval
- from / of
- create
Creating
Operators
Operators
const source$: Observable<number> = timer(1000);
const result$: Observable<number> = source$
.pipe(
filter(v => v % 3 === 1),
map(v => v * 7)
);
There's over 100 operators!
Do we even need custom ones?
Custom operators
- Creating specific operators based on existing ones
- Extract multiple operators
- more meaningful
- reusable
- Create missing pieces ourselves
We can benefit by
Operators
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
Operators
Under the hood
Custom operators
How do we put that in real life?
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))
Custom operators
Group multiple operators into one
const myOperator =
<T,R>(source: Observable<T>) =>
source.pipe(operator1, operator2)
Custom operators
Group multiple operators into one
const oddValueTimesSeven =
(num$: Observable<number>) =>
num$.pipe(

filter(v => v % 2 === 1),

map(v => v * 7),

)
Custom operators
Group multiple operators into one
const extractData =
<T>(response$: Observable<{data: T, loading: boolean}>)
=> response$.pipe(
isTruthy,
filter(response => !response.loading),
map(v => v.data),
)
Custom operators
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(…)},

})
})
Custom operators
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() }
})
)
Custom operators
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));
}
}
Custom operators
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!
Thank you!
Â
Any question?
Â
WarsawJS Workshop 38 - custom RxJS operators
By Chris Trześniewski
WarsawJS Workshop 38 - custom RxJS operators
- 281