Home > database >  Filter nested observable array RxJs Angular
Filter nested observable array RxJs Angular

Time:03-10

I am trying to filter some observable nested array in angular with the filter function in combination pipe function of the RxJs library.

Question: I only want to show the categories with surveys given by a specific date.

Simplified situation: My angular component has 3 radiobuttons (values 1,2,3). If i click on one of them it goes to my 'FilterChanged($event.value)' function. In this function i would like to filter the data that is provided by an api. This api at first provides all the categories. After retrieving the data i would like to filter according to the radio-button.

This is the data i get back from the api:

[
  {
    "category": "A",
    "surveys": [
      {
        "day": "1",
        "answers": [
          {
            "name": "1",
            "value": "a"
          },
          {
            "name": "2",
            "value": "b"
          },
          {
            "name": "3",
            "value": "c"
          }
        ]
      },
      {
        "day": "2",
        "answers": [
          {
            "name": "1",
            "value": "a"
          },
          {
            "name": "2",
            "value": "b"
          },
          {
            "name": "3",
            "value": "c"
          }
        ]
      },
      {
        "day": "3",
        "answers": [
          {
            "name": "1",
            "value": "a"
          },
          {
            "name": "2",
            "value": "b"
          },
          {
            "name": "3",
            "value": "c"
          }
        ]
      }
    ]
  },
    {
    "category": "B",
    "surveys": [
      {
        "day": "2",
        "answers": [
          {
            "name": "1",
            "value": "a"
          },
          {
            "name": "2",
            "value": "b"
          },
          {
            "name": "3",
            "value": "c"
          }
        ]
      },
      {
               "answers": [
          {
            "name": "1",
            "value": "a"
          },
          {
            "name": "2",
            "value": "b"
          },
          {
            "name": "3",
            "value": "c"
          }
        ]
      },
      {
        "day": "2",
        "answers": [
          {
            "name": "1",
            "value": "a"
          },
          {
            "name": "2",
            "value": "b"
          },
          {
            "name": "3",
            "value": "c"
          }
        ]
      }
    ]
  }
]

If radio button 1 is selected i would like to only show the category A and only show it has 1 survey because thats the only survey matching the filter.

This is what i have tried but does not work:

this.categories$ = this.allcategories$.pipe(map(categories => categories.filter(category => category.surveys.filter(survey => survey.day == '1'))));

and a lot more variations but it does not seem to work. I think i should not need a map because i am not transforming any data so filter should be fine but is not working for me so far.

I appreciate any help. Thanks

CodePudding user response:

This would work for you too:

let teste = [];
allcategories.forEach( category => { 
    category.surveys.forEach( survey => {
        if (survey.day == '1'){
            teste.push(survey)
        }
    })
})

CodePudding user response:

It depends how you are using the observable, here are 2 examples :

  • If you want to set the categories in a property, not as observable, you have to use the subscribe method like this:
this.subscription = this.categories$.subscribe({
  next: categories => this.categories = categories
});

then use this.categories. In this case do no not forget to call this subscription.unsubscribe() when destroying the component.

  • If you are using it in your component template with the async pipe it should work fine.

Remark: an observable is not activated if there is no subscribe (the async pipe does the subscribe and unsubscribe)

CodePudding user response:

The problem I am seeing here is: the filter function is not synchronous. This means, when you call the second filter, the first is answered with a Promise-like response. Since your code is not asynchronous, filter will respond with all the elements, or none.

To solve this problem, you would have to declare your function as an asynchronous function.

Here's some example on how to do so:

async function awesomeFilter(allcategories){
    return await allcategories.filter(category =>
        category.surveys.filter(survey => survey.day == '1').length 
    )
}

The survey.day would be your nested verification;

The .length would return to the first filter 0 if no correspondences are found or positive value if there are, telling the first filter where the correspondences are.

This way I was able to make it work. Hopefully it will help you.

CodePudding user response:

not positive what you're looking for, but it seems like you want to filter the outer array based on what's in the inner array and also filter the inner array, this can be achieved in one pass with reduce:

function filterOuterByInner(array, value) {
  return array.reduce((acc, v) => {
    const tmp = { ...v }; // create shallow copy
    tmp.surveys = tmp.surveys.filter(a => a.day === value); // filter surveys array by value
    if (tmp.surveys.length)
      acc.push(tmp); // add to acc array if any surveys after filter
    return acc;
  }, []);
}

then just use it in your map:

this.categories$ = combineLatest(this.allcategories$, this.value$).pipe(map(([categories, val]) => filterOuterByInner(categories, val)));
  • Related