2

I am trying to filter view of products but I don't know why original array is being modified, resulting in not correct filtering...

categories is an array with objects of categories {title, products[]}

categoriesView I want it to be a filtered array

filterByKeyword(keyword: string) {
    let k = keyword.toLowerCase();

    this.categoriesView = this.categories.filter((c) => {
      for(let q = 0; q < c.products.length; q++) {
        return c.products[q].title.toLowerCase().indexOf(k) >= 0;
      }
    });

    // filter products of each category:
    for(let q = 0; q < this.categoriesView.length; q++) {
      for(let z = 0; z < this.categories.length; z++) {
        if(this.categoriesView[q].title == this.categories[z].title){
          this.categoriesView[q].products = this.categories[z].products.filter((p) => {
              return p.title.toLowerCase().indexOf(k) >= 0;
          });
        }
      }
    }

    console.log("Categories View: ", this.categoriesView);
    console.log("Categories: ", this.categories);
}

First filter with categories works correctly. When I dive into products problems appear and original array is modified.

4 Answers 4

2

Sorry but I had to step in.

Your filtering and code are horrible.

Here it is, rewritten, clearer, simpler, and it might even should work :

filterByKeyword(keyword: string) {

    const k = keyword.toLowerCase();

    this.categoriesView = this.categories.filter(c => {
      let ret = false;
      for(let q of c.products) {
        if(q.title.toLowerCase().includes(k)) { ret = true; }
      }
      return ret;
    });

    // Even cleaner
    // this.categoriesView = this.categories.filter(c => c.products.find(p => p.title.toLowerCase().includes(k)));

    for (const view of this.categoriesView) {
      for (const cat of this.categories) {
        if (view.title === cat.title) {
          view.products = cat.products.filter(p => p.title.toLowerCase().includes(k));
        }
      }
    }

    console.log("Categories View: ", this.categoriesView);
    console.log("Categories: ", this.categories);
}
Sign up to request clarification or add additional context in comments.

13 Comments

Hey thank you! It's the result of finding proper solution over hours... It works nice, however after filtering once, original array has only 1 product instead of many.
Well this doesn't come from this piece of code, since nowhere you wrote somethign along the lines of this.categories = .... Are you sure the problem isn't coming from elsewhere ?
@trichetriche categories is an array of category objects { title, products: [ {...} ]. After fetching data from firestore this is the only operation that takes place.
And once fetched, is there more than one object in it ?
@trichetriche, yes
|
0

It should work:

filterByKeyword(keyword: string) {
    const k = keyword.toLowerCase();
    this.categoriesView = this.categories.map(x => Object.assign({}, x));
    this.categoriesView = this.categoriesView.filter((category) => {
        category.products = category.products.filter(
          (product)=> product.title.toLowerCase().includes(k));

        return category.products.length>0 }
      );
    if(this.categoriesView.length===0){
      this.categoriesView = this.categories.map(x => Object.assign({}, x));
    }
  }

1 Comment

Perfect! Works like charm :) Thanks!
0

In javascript, objects are passed by reference. So categoriesView actually IS a reference to your categories array and changing the properties in one will change both. If you want to create a separate object, you can use angular.copy()

this.categoriesView = angular.copy(this.categories.filter((c) => {
  for(let q = 0; q < c.products.length; q++) {
    return c.products[q].title.toLowerCase().indexOf(k) >= 0;
  }
}););

see more info here

3 Comments

Is there any replacement for AngularJS copy method in Angular2?
Use array destructuring to create a copy: newArray = [...oldArray];. BTW, angular.copy() is only for AngularJS (1.X) and not for Angular (2+)
@merlosy destructuring did not help
0

there is an issue with :

this.categories.filter( (c) => {...} )

Your array is not iterated properly as it returns at the first round. You should concider using a temporary boolean as a buffer for your loop, then return it.

Also, concider refactor your code with Array.prototype.map and Array.prototype.reduce, it will make your code a lot more readable

this.categoriesView = this.categories.filter(c => {
    return c.products.reduce( (acc, product) => {
        return acc || product.title.toLowerCase().includes(k);
    }, false);
});

3 Comments

As I said in the rejection of your edit on my post, reduce isn't a function a beginner can use. If you really want to make your code cleaner and more readable, consider using find instead, and rely on truthy/falsy values.
the concept of reduce is quite straightforward when you look at the docs.. btw I always concider myself a beginner
And the concept of filtering an array is pretty straightforward without reading any doc, yet he spent several hours on it. I'm not saying you're wrong, I agree with you : you just can't advise advise it to someone who is already struggling.

Your Answer

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