1

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.

StackBlitz

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 a product object

Here's the structure of selectedFilters enter image description here

A big thank you in advance :-).

4
  • can you create a minimal reproducible snippet ? i.e. StackBlitz. So that people can understand your issue a bit better Commented May 23, 2020 at 14:23
  • if you're using ngrx, it sounds like there should be some bit of state like filteredProducts that uses both the products and filters from the store. Commented May 23, 2020 at 15:25
  • @SupunDeSilva Thanks for interesting to my problem. Sure, there's my code :-) Commented May 23, 2020 at 16:23
  • Well, if possible add declarations of GetAllFilters and FiltersState. So if there is something wrong it is easier to spot. Also you have a BaseComponent not sure if it is relevant for the issue. In a nut-sell make it easier for the other users to find the issue without having to make assumptions. Commented May 23, 2020 at 23:44

1 Answer 1

1

Here's a stackblitz with an example that works.

To sum it up:

  1. We listen to changes in both products and filters, and combine them into a single observable using combineLatest:
    const productsAndFilters: Observable<[Product[], ProductFilter[]]> = combineLatest([
      this.products$,
      this.filters$.pipe(startWith(initialFilters)),
    ]);
  1. Then we pipe the combined observable into a function that actually does the filtering job
    this.filteredProducts$ = productsAndFilters.pipe(
      map(([products, filters]) => {
        return AppComponent.filterProducts(products, filters);
      }),
    )
  1. Every time products$ emits a new value, we will update filteredProducts$. Every time filters$ emits a new value, we update them too.

  2. 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.

Sign up to request clarification or add additional context in comments.

2 Comments

A real thanks for your help and explanations! I think i understood what you wanna however I failed to make the logic work. I created a stackBlitz stackblitz.com/edit/…
After updating filters list, i log the filtered products and i always get the same results with all products

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.