RxJS 6 filter and map an observable array of items
Solution 1
mergeAll merges an observable of observables.
You want
cards: Observable<PlanCard[]> = plans.pipe(
map(plans => plans.filter(plan => plan.is_active)), // An array comes down the pipe, you don't want to filter out the arrays but the elements in the array
map(plans => plans.map(plan => new PlanCard(plan, 1, 1))), // An array of active plans can be mapped into an array of PlanCards
reduce((results, plans) => [...results, ...plans], []) // Reduce all the arrays into a single array
);
You can use scan instead of reduce if you want the accumulator of the reduce to come down the pipe each time a new array comes down, reduce only fires after all arrays have come down the pipe.
I don't usually use a filter followed by a map but it is easier to see what is going on, I would usually do it in a single step with a reduce,
plans => plans.reduce((results, plan) => plan.is_active ? [...results, new PlanCard(plan, 1, 1)] : results, [])
Is the same as a filter followed by a map but does it in a single iteration instead of two.
It can be quite confusing when you have observables of arrays, you need to consider what functions you want to apply to the observable and which you want to apply to the array that comes down the pipe.
const { from } = rxjs;
const { map, reduce } = rxjs.operators;
const plans = from([
[{id: 1, is_active: true}, {id: 2, is_active: false}, {id: 3, is_active: true}, {id: 4, is_active: true}],
[{id: 5, is_active: true}, {id: 6, is_active: true}],
[{id: 7, is_active: false}, {id: 8, is_active: true}, {id: 9, is_active: true}],
[{id: 10, is_active: true}, {id: 11, is_active: false}, {id: 12, is_active: true}, {id: 13, is_active: false}],
]);
function PlanCard(plan) {
this.id = plan.id;
}
plans.pipe(
map(plans => plans.reduce((results, plan) => plan.is_active ? [...results, new PlanCard(plan, 1, 1)] : results, [])),
reduce((results, plans) => [...results, ...plans], [])
).subscribe(results => { console.log(results); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.min.js"></script>
Solution 2
This had me scratching my head for a while, so a brief example which may be of help if you're looking to filter an observable array of objects. Kudos to @Adrian for pointing me in the right direction.
If you have a service that returns an observable array of objects, e.g:
dataSub = new BehaviorSubject<Array<object>>([]);
getItems(): Observable<Array<object>> {
return this.dataSub.asObservable();
}
You can filter with a given criteria with the following. In this example, I want only elements where the property 'selected' is true.
constructor() {
this.getItems()
.pipe(
map(items => items), /* Don't forget to add this! */
filter(item => item['selected'] === true)
)
.subscribe(data => {
console.log(data);
});
}
The (rookie) error which led to my confusion was omitting the 'map', so the filter function was being applied to the whole array, rather than each element.
Related videos on Youtube
Comments
-
Jan Nielsen almost 2 years
In my Angular 7.1.1 application with RxJS 6.3.3, an observable array of plans should be transformed into an observable array of active plan cards. The active filter is:
filter(plan => plan.is_active)
and the transformation of plan to card is:
map(plan => new PlanCard(plan, 1, 1))
so, presumable, the answer is something like:
plans: Observable<Plan[]> = getPlans(); cards: Observable<PlanCard[]> = plans.pipe( mergeAll(), filter(plan => plan.is_active), map(plan => new PlanCard(plan, 1, 1)), toArray() );
But alas,
cards
is empty.The plans can correctly be transformed to cards via:
cards: Observable<PlanCard[]> = plans.pipe( map(plans => plans.map(plan => new PlanCard(plan, 1, 1))) );
but adding a filter prior does not filter the plans, unfortunately:
cards: Observable<PlanCard[]> = plans.pipe( filter(plans => plans.filter(plan => plan.is_active)), map(plans => plans.map(plan => new PlanCard(plan, 1, 1))) );
Any better ideas?
export const planData: Plan[] = [{ name: 'P1', is_active: true }, { name: 'P2', is_active: true }, { name: 'P3', is_active: true }, { name: 'P4', is_active: true }, { name: 'P5', is_active: false }]; export const plans = createObservable(planData);
-
user184994 over 5 yearsYou need to map the array as well then, so something like
map(plans => plans.map(plan => new PlanCard(plan, 1, 1))
, and it looks like you want to use the arraysfilter
function, not the Observables -
Jan Nielsen over 5 yearsRight, @user184994 -- I started with that approach -- I'll update my question with that failed attempt as well.
-
-
Alex Okrushko over 3 years
map(items => items)
is a noop -
Aouidane Med Amine over 3 yearsreplace the map & filter operators with : map((items: Item[]) => items.filter((item: Item) => item.selected=== true)))