Reputation: 62
I'm trying to filter an observable of products with an array of filters but i really don't know how..
Let me explain, I would like to set the result of my filtering to filteredProducts. For filtering i have to check, for each filter, if the product's filter array contains the name of the filter and if the products values array's contains filter id.
For the moment, the filter works but only with the last selected filter and i'd like to filter products list with all filters in my selectedFilters array. I can have one or multiple filters.
export class ProductsFilterComponent extends BaseComponent implements OnInit {
@Select(FiltersState.getAllFilters) filters$: Observable<any>;
@Input() products$: Observable<Product[]>;
filteredProducts$: Observable<Product[]>;
public selectedFilters = [];
constructor(
private store: Store) { super(); }
ngOnInit() {
this.store.dispatch(new GetAllFilters());
}
private filterProducts() {
this.filteredProducts$ = this.products$.pipe(
map(
productsArr => productsArr.filter(
p =>
p.filters.some(f => this.selectedFilters.some(([selectedF]) => selectedF === f.name.toLowerCase()) // Filter name
&& f.values.some(value => this.selectedFilters.some(([, filterId]) => filterId === value)) // Filter id
)
)
)
);
this.filteredProducts$.subscribe(res => console.log('filtered:', res));
}
}
Here's the structure of a product object
Here's the structure of selectedFilters
A big thank you in advance :-).
Upvotes: 1
Views: 2654
Reputation: 4079
Here's a stackblitz with an example that works.
To sum it up:
combineLatest
: const productsAndFilters: Observable<[Product[], ProductFilter[]]> = combineLatest([
this.products$,
this.filters$.pipe(startWith(initialFilters)),
]);
this.filteredProducts$ = productsAndFilters.pipe(
map(([products, filters]) => {
return AppComponent.filterProducts(products, filters);
}),
)
Every time products$
emits a new value, we will update filteredProducts$
. Every time filters$
emits a new value, we update them too.
Note that combineLatest
will NOT emit until EACH observable has emitted at least once. For this reason, if your "selectedFilters" do not emit anything in the beginning, you can use startWith
operator to create an observable with a starting value. This makes sure that combineLatest
will emit a value as soon as products$
emits a value.
I've also moved the filtering code into a static function:
private static filterProducts(products: Product[], filters: ProductFilter[]) {
// we can combine filters into a single function
// so that the code a tiny bit more readable
const matchesAllFilters = (product: Product) => filters.every(
([filterName, filterValue]) => product.filters
.some(f => f.name === filterName &&
f.values.some(value => value === filterValue))
);
return products.filter(matchesAllFilters);
}
In my example I assume that you want ALL filters to be satisfied. If you instead want "AT LEAST ONE" filter to be satisfied, you can change filters.every
to filters.some
.
Upvotes: 1