Nona Blog

Writing a filter with RxJS

We’ve all been there. The UI you’re busy implementing contains a list that can be filtered by multiple inputs, in various ways. Implementing this with an imperative style can lead to state-laden components with a wide surface area for bugs.

We’ve all been there. The UI you’re busy implementing contains a list that can be filtered by multiple inputs, in various ways. Implementing this with an imperative style can lead to state-laden components with a wide surface area for bugs.

Or you could build it using Rx streams and operators.

What is RxJS?

RxJS is a Javascript implementation of the reactive extensions (Rx) API. The API allows you to treat your data sources as streams and offers a rich library of operations to manipulate and combine those streams to get the functionality that you need.

The Setup

// We start with a representation of the total list of items
const items = new BehaviourSubject([]);

// And represent the current value of the two filters using
// BehaviourSubjects 
const filter1 = new BehaviourSubject(null);
const filter2 = new BehaviourSubject(null);

// combineLatest allows us to compose a stream of streams
const combineFilters = (
  items, 
  filter1, 
  filter2
) => combineLatest(
  items, 
  filter1, 
  filter2, 
  runFilters(items, filter1, filter2)
);

const runFilters = (items, filter1, filter2) => runFilter1(
  runFilter2(items, filter2), 
  filter1
);

const runFilter1 = (items, value) => {
  if (!value) {
    return items;
  }
  
  items.filter(item => item.selected)
};


const runFilter2 = (items, value) => {
  if (!value) {
    return items;
  }
  
  items.filter(item => item.name !== 'He who shall not be named')
};

const filteredItems = combineFilters.subscribe();

What’s going on here?

We’re using two Rx concepts to get us what we want.

  1. `BehaviourSubject` is a specific kind of observable stream that can start off with a default value.
  2. `combineLatest` takes a number of observables and returns a new observable with the result of a calculation that you pass to it. In this case this returns the list of items filtered by each filter.

What we’re left with is an observable of the filteredItems that we can use to power the UI.

The last piece of the puzzle is to update the filters. This is where the Rx approach really shines because all we need to do is publish a next item to the stream that represents the value of that filter over time and the filteredItems backed by combineLatest will update automatically.

filter1.next(newValue);

Okay, but why?

It takes a while to get your brain around but once you start thinking about your data — whether on the client or the server side — as streams to be composed and manipulated it opens up many new patterns and you’ll find that many problems that can be quite sticky become much more tractable through this lens.

To me the primary benefit of this approach is that it allows you to represent more of your application purely declaratively which makes it easier to reason about.

James Smith

James Smith

Add comment