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);
To convert from array to observable you can use Rx. Observable. from(array) . To convert from observable to array, use obs.
RxJS filter() Filtering Operator The RxJS filter() operator is like the well-known Array Array . prototype. filter() method. This operator takes values from the source Observable, passes them through a predicate function and only emits those values that get TRUE.
RxJS map() Transformation Operator RxJS map() operator is a transformation operator used to transform the items emitted by an Observable by applying a function to each item. It applies a given project function to each value emitted by the source Observable and then emits the resulting values as an Observable.
http. get(apiURL) returns an Observable . map is an observable operator which calls a function for each item on its input stream and pushes the result of the function to its output stream. In this case each input item is a Response object.
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With