8

Let's consider the following simplified situation:

  • We have an Observable apples of type Observable < Apple >
  • Every Apple object has a method isRotten() which returns an observable of type Observable < Boolean > which is guaranteed to emit at least one boolean value.

I want to filter the apples observable such that the rotten apples don't pass the filter. More precisely, an apple A passes the filter if and only if the first item emitted by A.isRotten() is false. What is the best way to implement this filter?

After some thinking I could come up with this:

apples
    .concatMap(apple => 
        apple.isRotten()
            .first()
            .filter(bool => bool)
            .map(bool => apple))

Which is written in javascript. ( ... => ... is a function). This works, but I think it is rather lengthy and difficult to understand. Is there a better way to do this kind of thing?

Ward Beullens
  • 393
  • 5
  • 18

3 Answers3

4

What you've got is fine and, tbh, I can't think of a more concise way of doing it. I'd probably use flatMap rather than concatMap if out-of-order apples aren't an issue.

If readibility is an issue for you, just move the implementation into it's one function (eg. filterObservable that accepts a function that takes a value and returns an IObservable<bool>)

Richard Szalay
  • 83,269
  • 19
  • 178
  • 237
  • Thanks for your answer, I was hoping for something like filterObservable to exist. But I guess i'll have to implement it myself. – Ward Beullens Jul 24 '16 at 10:16
0

One way to achieve that is like this, sorry I didn't get to adapt this to fruit filtering:

const orders$: Observable<Order[]> = searchOrders(...);

const filteredOrders$ = orders$.pipe(switchMap((orders) => {

    // take all orders and determine visibility based on an observable
    const visibilityRules = orders.map(o => {

        return {                    
            order: o,
            visible$: o.isPaidFor$   // this is an observable on an Order object
        };
    });

    const filtered = visibilityRules.map(o => o.visible$.pipe(map(visible => visible ? o.order : undefined )));
    return (filtered.length == 0) ? of([]) : combineLatest(filtered).pipe(map(combined => combined.filter(x => x != undefined)));

}));

This filters 'paidFor' orders and emits a new array every time an order becomes paid or unpaid.

Note: If the isPaidFor$ observable can't change between searches then this whole exercise is pointless because there would be no reason to provide a 'live view' like this. This only makes sense if the observable can actually change between search results.

This could be extended to much more complicated rules if needed (such as adding filtering checkboxes) - that's why I created the intermediate visibilityRules array - which strictly speaking is just for readability.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
-3

You can do something like this:

var seq = Rx.Observable.from([1, 2, 3, 4, 5, 6])
    .filter(x => {
        let isRotten = true;
        Rx.Observable.just(x % 2 === 0)
            .first()
            .subscribe(val => isRotten = val);

        if (isRotten) {
            return x;
        }
    });

seq.subscribe(x => console.log(x));
Toan Nguyen
  • 11,263
  • 5
  • 43
  • 59
  • Are you sure? I don't think this works because x.isRotten() does not return a boolean, it returns an Observable that emits booleans. – Ward Beullens Jul 24 '16 at 00:25
  • I made the following fiddle, it verifies that the filter operator does not work with Observables. http://jsfiddle.net/pgcrwauj/ – Ward Beullens Jul 24 '16 at 00:33
  • @WardBeullens Please see the updated code. I didn't get your question at first. – Toan Nguyen Jul 24 '16 at 03:52
  • I'm sorry, but I dont think your approach is any good. It only works (honestly im supprised it works at all) because the Observable.just resolves really fast. If you add a delay (as if the method had to do a http call or something) it no longer works. http://jsfiddle.net/8qu2c29j/ – Ward Beullens Jul 24 '16 at 10:04
  • @WardBeullens If the code has to do extra work to get the inner Observable, then I would do thing differently. Specifically, I would look at map to get the immediate value + original value and then filter on the output of the map. – Toan Nguyen Jul 24 '16 at 10:18