How to filter out observable values based on observable property of each value

Viewed 1331

Suppose I have an Observable of items

let items$ = Observable<Item[]>

and each Item has a property isSelected$, which itself is an Observable:

private isSelected$: Observable<boolean>

What would be the best way to receive a list of all items that are currently selected? My solution doesn't look quite right:

items$.pipe(
    switchMap(items =>
        combineLatest(items.map(item =>
                item.isSelected$.pipe(
                    map(isSelected => ({item: item, isSelected: isSelected})))),
            map(items => items.filter(item => item.isSelected).map(item => item.item))
        )))

It works and I have used it before, but it's such a complicated construct for something I have to do quite often. There must be a better way.

Note: The subscriber needs a list of all the selected items, not a stream of individual items.

Example: items$ would emit the following list:

[{id: 1, isSelected$: of(true)}, 
    {id: 2, isSelected$: of(false)}, 
    {id:3, isSelected$: of(true)}]

And as a result in our subscribe, we would like to get the list:

[{id: 1}, {id: 3}]
1 Answers

You may want to try something like this

.pipe(
    switchMap(
        items => from(items).pipe(
            mergeMap(item => item.isSelected$.pipe(map(sel => ({sel, item})))),
            filter(data => data.sel),
            map(data => ({id: data.item.id})),
            toArray()
        )
    ),
)

First we take the notification of items$, which is an Array, and transform it into an Observable which emits one item at the time.

The key then is in the function passed in as parameter to mergeMap.

item.isSelected returns the Observable<boolean> to be used for filtering. To such Observable we apply a transformation via map to turn it into an object which holds 2 properties: the selection criteria and the entire item.

The rest is just filtering based on the selection criteria and then transforming again to return the item.

I have tested the above code with the following test data

const items$ = of(
    [
        {id: 1, isSelected$: of(true)},
        {id: 2, isSelected$: of(false)},
        {id: 3, isSelected$: of(true)},
        {id: 4, isSelected$: of(true)},
    ],
    [
        {id: 10, isSelected$: of(true)},
        {id: 20, isSelected$: of(true)},
        {id: 30, isSelected$: of(false)},
        {id: 40, isSelected$: of(false)},
    ]
);
Related