Macks
Macks

Reputation: 1776

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 here enter 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!

Upvotes: 5

Views: 1739

Answers (4)

Felipe Skinner
Felipe Skinner

Reputation: 16626

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

Upvotes: 1

Oliver Weichhold
Oliver Weichhold

Reputation: 10296

You could take a look at WebRx's List-Projections as well.

Live-Demo

Disclosure: I am the author of the Framework.

Upvotes: 0

Eryk Napierała
Eryk Napierała

Reputation: 506

You can look how I did it here: https://github.com/erykpiast/autocompleted-select/

It's end to end solution, with grabbing user interactions and rendering filtered list to DOM.

Upvotes: 0

paulpdaniels
paulpdaniels

Reputation: 18663

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

Upvotes: 5

Related Questions