LiveLoveApp logo

Higher-order Function Operator

Higher-order Functions

Let's quickly recap what a higher-order function is, and take note of the higher-order Observable operators in RxJS.

To quote wikipedia:

A higher-order function is a function that does at least one of the following:

  • takes one or more functions as arguments (i.e. procedural parameters),
  • returns a function as its result.

Let's take a look at an example of a higher-order function in JavaScript. In this example we'll use a higher-order function in order to calculate values.

type Operation = (a: number, b: number) => number;

const add: Operation = (a: number, b: number) => a + b;
const subtract: Operation = (a: number, b: number) => a - b;

const calculator = (start = 0) => (operation: Operation, operand: number) =>
  operation(start, operand);

let sum = calculator(5)(add, 4);
console.log(sum);

sum = calculator(5)(subtract, 4);
console.log(sum);

See example on codesandbox

Let's go through the code example above that uses a higher-order function:

  • First, we declare a new Operation type. An Operation is a function that accepts two number value and returns a number.
  • Then, we define two Operation functions: add() and subtract().
  • The calculator is a higher-order function that has a start value and returns a function, which accepts an Operation function and the operand number value. The "inner" function invokes the operation() function using the start and operand values.
  • Now, let's look at two implementation that use the calculator() higher-order function. First, we use the add() function, and then we use the subtract() function.

Higher-order Observables

Similar to a higher-order function, a higher-order Observable is an Observable that emits one or more Observables.

Let's look at a simple implementation of a higher-order Observable:

import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

const higherOrderObservable = new Observable((observer) => {
  let i = 0;
  const add = new Observable((_observer) => _observer.next(i++ * 10));
  const handle = window.setInterval(() => observer.next(add), 1000);
  return () => window.clearInterval(handle);
});

higherOrderObservable
  .pipe(
    switchMap((observable: Observable<number>) =>
      observable.pipe(tap(console.log))
    )
  )
  .subscribe();

See example on codesandbox

In this example we are creating a higher-order Observable that emits an Observable:

  • First we create a new Observable instance. Let's refer to this Observable as the "outer" Observable.
  • Then, we declare i to start with 0.
  • The add constant is a new Observable that emits a next notification that multiplies the incremented i value by 10. Let's refer to this Observable as the "inner" Observable.
  • We use window.setInterval() to emit to the Observer the inner add Observable every 1000 milliseconds.
  • When the outer Observable is unsubscribed from the teardown function is invoked, which clears the interval.
  • Finally, we subscribe to the outer Observable. The switchMap() operator receives an Observable as the next notification. The switchMap() subscribes to the inner Observable and maps to the next notification emitted by the inner Observable.

Higher-order Function as Operator

Having reviewed both higher-order functions and Observables, let's bring these two concepts together to create a new operator using a higher-order function.

The goal of the operator is to calculate the characters entered into a textarea per second.

import { fromEvent, Observable, from } from 'rxjs';
import { count, filter, map, mergeMap, windowTime } from 'rxjs/operators';

function characterCount(timeout = 1000) {
  return (source: Observable<string>) =>
    source.pipe(
      windowTime(timeout),
      mergeMap((value) =>
        from(value).pipe(
          count(),
          filter((count) => count > 0)
        )
      )
    );
}

const input = document.getElementById('input') as HTMLTextAreaElement;
const output = document.getElementById('output') as HTMLInputElement;
fromEvent(input, 'keyup')
  .pipe(
    map(() => input.value),
    characterCount()
  )
  .subscribe({
    error: console.error,
    next: (count) => (output.value = count.toString()),
    complete: () => console.log('complete')
  });

See example on codesandbox

Let's review our characterCount operator.

  • First, we can see right away that we have created a higher-order function. The characterCount function accepts an optional timeout argument and returns a new function.
  • The returned function accepts the source Observable.
  • We are using the windowTime operator to slice up the source Observable based on the timeout and emit a new Observable.
  • The mergeMap() operator's source Observable is the string of characters entered within the window of the timeout. We use the from() operator to create a new Observable that emits for each character, and then count() the number of next notification, filtering out those counts that are not greater than 0.