Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a filterable list with RxJS

I'm trying to get into reactive programming. I use array-functions like map, filter and reduce all the time and love that I can do array manipulation without creating state.

As an exercise, I'm trying to create a filterable list with RxJS without introducing state variables. In the end it should work similar to this:

enter image description hereenter image description here

I would know how to accomplish this with naive JavaScript or AngularJS/ReactJS but I'm trying to do this with nothing but RxJS and without creating state variables:

var list = [
  'John',
  'Marie',
  'Max',
  'Eduard',
  'Collin'
];

Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
  .map(function(e) { return e.target.value; });

// i need to get the search value in here somehow:
Rx.Observable.from(list).filter(function() {}); 

Now how do I get the search value into my filter function on the observable that I created from my list?

Thanks a lot for your help!

like image 996
Macks Avatar asked May 29 '15 13:05

Macks


2 Answers

You'll need to wrap the from(list) as it will need to restart the list observable again every time the filter is changed. Since that could happen a lot, you'll also probably want to prevent filtering when the filter is too short, or if there is another key stroke within a small time frame.

//This is a cold observable we'll go ahead and make this here
var reactiveList = Rx.Observable.from(list);

//This will actually perform our filtering
function filterList(filterValue) {
  return reactiveList.filter(function(e) {
   return /*do filtering with filterValue*/;
  }).toArray();
}


var source = Rx.Observable.fromEvent(document.querySelector('#filter'), 'keyup')
  .map(function(e) { return e.target.value;})

  //The next two operators are primarily to stop us from filtering before 
  //the user is done typing or if the input is too small
  .filter(function(value) { return value.length > 2; })
  .debounce(750 /*ms*/)

  //Cancel inflight operations if a new item comes in.
  //Then flatten everything into one sequence
  .flatMapLatest(filterList);

//Nothing will happen until you've subscribed
source.subscribe(function() {/*Do something with that list*/});

This is all adapted from one of the standard examples for RxJS here

like image 190
paulpdaniels Avatar answered Nov 14 '22 04:11

paulpdaniels


You can create a new stream, that takes the list of people and the keyups stream, merge them and scans to filter the latter.

const keyup$ = Rx.Observable.fromEvent(_input, 'keyup')
  .map(ev => ev.target.value)
  .debounce(500);

const people$ = Rx.Observable.of(people)
  .merge(keyup$)
  .scan((list, value) => people.filter(item => item.includes(value)));

This way you will have:

-L------------------ people list

------k-----k--k---- keyups stream

-L----k-----k--k---- merged stream

Then you can scan it. As docs says:

Rx.Observable.prototype.scan(accumulator, [seed])

Applies an accumulator function over an observable sequence and returns each intermediate result.

That means you will be able to filter the list, storing the new list on the accumulator.

Once you subscribe, the data will be the new list.

people$.subscribe(data =>  console.log(data) ); //this will print your filtered list on console

Hope it helps/was clear enough

like image 23
Felipe Skinner Avatar answered Nov 14 '22 03:11

Felipe Skinner