Filter nested observable array RxJs Angular - javascript

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.
Whyd doesn't this code works?? the update filter gets triggerd at the radiobox change event. For me this is much more readable than the reduce with spreader functions.
updateFilter(filterDays : number): void {
var filterDate = this.getFilterDate(filterDays);
this.surveyTypes$ = this.allSurveyTypes$.pipe(map((types) => this.filterSurveyTypes(types, filterDate)));
}
filterSurveyTypes(types : SurveyType[], filterDate : Date) : SurveyType[] {
return types.filter(type => type.surveys.filter(survey => moment(survey.filledInDate).isSameOrAfter(filterDate)).length);
}
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

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)));

This would work for you too:
let teste = [];
allcategories.forEach( category => {
category.surveys.forEach( survey => {
if (survey.day == '1'){
teste.push(survey)
}
})
})

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)

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.

Related

Getting the key in every JSON object

I am fairly new to Vue and JS but I am making API calls and getting a JSON response then sending the response to an empty array. How do I get the ID of each object in the array?
The array that the response is being pushed to is structured like this
groups: [
{
"id": "0",
"name": "a",
"price": 5
},
{
"id": "1",
"name": "b",
"price": 5
},
{
"id": "2",
"name": "c",
"price": 5
}
]
I'd like to pull the Id of each object and push the values to an empty array
for(var group in this.groups) {
if (this.groups.hasOwnProperty(0)) {
this.group = this.groups[0];
this.groupsId.push(this.innerObj);
}
}
The error I'm getting is saying Cannot read property '0' of undefined at eval
Ideally I'd like an array that has all the Ids of each object.
this.groups.hasOwnProperty(0) should be group.hasOwnProperty('id')
Use Array.prototype.map() to iterate over an array of objects and collect every ID into a new array:
const res = {
groups: [{
"id": "0",
"name": "a",
"price": 5
},
{
"id": "1",
"name": "b",
"price": 5
},
{
"id": "2",
"name": "c",
"price": 5
}
]
};
const ids = res.groups.map(obj => { // you use this.groups
if(obj.hasOwnProperty('id')) return obj.id;
});
console.log(ids)
There is the Array.map() method:
this.groupsId = this.groups.map(i => i.id);
If you already have elements in this.groupsId you can append the ids using Array.concat():
this.groupsId = this.groupsId.concat(this.groups.map(i => i.id));
You can use Array.prototype.reduce to loop and check if there's id.
const groups = [
{"name": "a","price": 5},
{"id": "1","name": "b","price": 5},
{ "id": "2","name": "c","price": 5}
];
const list = groups.reduce((groupIds, group) => group.id ? [...groupIds, group.id] : groupIds, []);
console.log(list);

Need to make on object in Javascript based on an existing object which is efficient considering its performance

{
"name": "test name",
"description": "test desc",
"data_table_id": 3,
"column_0": {
"value": "1",
"label": "name"
},
"condition_0": {
"value": "101",
"label": "Is equal to"
},
"column_1": {
"value": "2",
"label": "age"
},
"condition_1": {
"value": "102",
"label": "Is less than"
}
}
I have the above object in JavaScript. From this object I need to create the following object. Need to find a way which is good from performance point of view. The below conditions array is based on the object starting with 'column_' in the above object.
For example: if there are column_0, column_1, column_2, the length of conditions array will be 3. These columns will be coming dynamically, can be from 0-n, n = any integer >= 0. (i.e. column_0 - column_n)
The same condition applies for condition_0, condition_1. Also, condition_0 is always associated with column_0, condition_1 is always associated with column_1 ans so on.
{
"name": "test name",
"description": "test desc",
"data_table_id": 3,
"conditions" : [
{
"column_id": 1, // column_0.value
"column_name": "name", // column_0.label
"condition_id": 101 // condition_0.value
},
{
"column_id": 2, // column_1.value
"column_name": "age", // column_1.label
"condition_id": 102 // condition_1.value
}
],
}
extract the conditions using ...rest, reduce the Object.entries , construct the data structure and push it to the resulting array, finally put everything back together :
const data = {
"name": "test name",
"description": "test desc",
"data_table_id": 3,
"column_0": {
"value": "1",
"label": "name"
},
"condition_0": {
"value": "101",
"label": "Is equal to"
},
"column_1": {
"value": "2",
"label": "age"
},
"condition_1": {
"value": "102",
"label": "Is less than"
}
}
const {
name,
description,
data_table_id,
...rest
} = data;
const conditions = Object.entries(rest).reduce((all, [key, obj]) => {
if (key.startsWith('condition')) {
const id = key.split('_')[1];
const condition = {
"column_id": rest[`column_${id}`].value,
"column_name": rest[`column_${id}`].label,
"condition_id": obj.value,
}
all.push(condition)
}
return all;
}, []);
const result = {
name,
description,
data_table_id,
conditions
}
console.log(result)

How to handle data binding on a dynamic set of checkboxes

I'm trying to create an angular directive making me able to select from a list of items group by category. Each item should be selectable using a checkbox.
The input data to the directive is something like
[
{
"id": "1",
"name": "category1",
"items": [
{
"id": "1",
"name": "item1"
},
{
"id": "2",
"name": "item2"
},
{
"id": "3",
"name": "item3"
},
{
"id": "4",
"name": "item4"
}
]
},
{
"id": "2",
"name": "category2",
"items": [
{
"id": "5",
"name": "item5"
},
{
"id": "6",
"name": "item6"
}
]
}
]
And the object of pre-checked items is:
{
"1": [
"2",
"4"
]
}
Here item with id 2 and 4 from category with 1 should be pre-checked.
My code results in this view:
The checked-state is handled using the ng-checked directive:
<input id="item-{{item.id}}" value="{{item.id}}" type="checkbox" ng-checked="isSelected(cat,item)">
When checking/unchecking an item, the selected items object should be updated to reflect the current state. How can I handle this? Should all this be structured in a different way?
See my plumber: http://plnkr.co/edit/6fbfZnQCq5fq1zDp8VIB.
As always, there is multiple ways to achieve this. My suggestion:
Use ng-model on your inputs:
<input ng-model="selected[cat.id][item.id]"
id="item-{{item.id}}"
value="{{item.id}}"
type="checkbox"
ng-checked="selected[cat.id][item.id]">
This will require a slight modification of your selectedItems property (it is now an object instead of an array):
$scope.selectedItems = {
"1": {
"2": true,
"4": true
}
};
The ng-checked in the HTML will automatically check the items which are marked true.
I'm not sure how you want to handle the selection of categories, but I hope this will give you an idea!
Check the updated Plunker.

Sorting an array of JavaScript objects by sub array property/value

I have the following data being returned from a server (the structure of this data is something that I do not have control over)...
var data = {
"TrackingResults": [
{
"Name": "Pack One",
"Products": {
"Product": [
{
"ProductName": "Soccer Ball"
},
{
"ProductName": "Tennis Racket"
},
{
"ProductName": "Gold Putter"
}
]
},
"status": "Despatched",
"Location": "Alabama",
"Type": "Parcel"
},
{
"Name": "Pack Two",
"Products": {
"Product": [
{
"ProductName": "Backet Ball Hoop"
},
{
"ProductName": "Base Ball Glove"
}
]
},
"status": "Despatched",
"Location": "Florida",
"Type": "Parcel"
}
]
};
I would like to be able to sort each Tracking Result by the first Product Name. I can't find any code that will sort by a sub array property/value.
You should use the Array.sort method with a custom comparator function:
var resultsComparator = function (res1, res2) {
var prod1 = res1.Products.Product[0].ProductName;
var prod2 = res2.Products.Product[0].ProductName;
return prod1.localeCompare(prod2);
}
This way the ordering is based on the current locale of the web browser. You just pass the function to the sort method:
data.TrackingResults.sort(resultsComparator);
You need to write it manually like: (with the hint on localeCompare from meskobalazs's comment)
var result = data.TrackingResults.sort(function(a,b){
return a.Products.Product[0].ProductName.localeCompare(b.Products.Product[0].ProductName)
});
This should work for sorting TrackingResults

Format Array into new Array

I have an array of objects. I would like to reformat into a new array but am not sure how to begin. I have jQuery and Underscore available.
Here is my original array:
var myArray = [
{
"name": "Product",
"value": "Car"
},
{
"name": "Product",
"value": "Boat"
},
{
"name": "Product",
"value": "Truck"
},
{
"name": "Color",
"value": "Blue"
},
{
"name": "Location",
"value": "Store"
}
];
Here is what I am trying to make the new Array look like:
var newArray = [
{
"name": "Product",
"value": "Car Boat Truck"
},
{
"name": "Color",
"value": "Blue"
},
{
"name": "Location",
"value": "Store"
}
];
In the newArray the Products are all in one object.
You can use the groupBy method to get all the elements with the same name together, then map to transform them into what you want. And pluck is useful here to combine the values in the output array.
Here's quick, simple solution:
var newArray = _.chain(myArray)
.groupBy("name")
.map(function(a) {
return {
"name": a[0].name,
"value": _.pluck(a, "value").join(" ")
};
})
.value();
Demonstration
And just for completeness, here's the non-chained version:
var newArray = _.map(_.groupBy(myArray, "name"), function(a) {
return {
"name": a[0].name,
"value": _.pluck(a, "value").join(" ")
};
});
Here's a more generalized solution that's reusable and not hard-coded. This way, you can create multiple groupBy methods for different properties of different object collections, then join the properties that you require. jsFiddle
function groupBy(groupBy) {
return function(source, joinOn) {
return _.each(_.groupBy(source, groupBy), function(val, key, context){
context[key] = _.pluck(val, joinOn).join(' ');
});
};
}
var groupByNameOn = groupBy('name');
console.log(groupByNameOn(arr, 'value'));

Categories