I am trying to use an array map to filter a object a bit further to prepare it to send to the server to for saving. I can filter to 1 key value, which is great, but I want to take it 1 step further and check them against a boolean inside.
So, right now this is what I have -
$scope.appIds = $scope.applicationsHere.map( function(obj){ if(obj.selected == true){ return obj.id; } });This works great for pulling out the id's, however I don't want to push them in this new array if they their selected value == false, so I put a conditional to filter further. This somewhat works, I get an array of id's, but the id's that have .selected == false are still in the array, just with the value of null. So If I have 4 items in the object and 2 of them are false it looks like this -
appIds = {id1, id2, null, null};My question is - is there a way to do this without the nulls being put in there. Thanks for reading!
14 Answers
You're looking for the .filter() function:
$scope.appIds = $scope.applicationsHere.filter(function(obj) { return obj.selected; });That'll produce an array that contains only those objects whose "selected" property is true (or truthy).
edit sorry I was getting some coffee and I missed the comments - yes, as jAndy noted in a comment, to filter and then pluck out just the "id" values, it'd be:
$scope.appIds = $scope.applicationsHere.filter(function(obj) { return obj.selected; }).map(function(obj) { return obj.id; });Some functional libraries (like Functional, which in my opinion doesn't get enough love) have a .pluck() function to extract property values from a list of objects, but native JavaScript has a pretty lean set of such tools.
You should use Array.prototype.reduce to do this. I did do a little JS perf test to verify that this is more performant than doing a .filter + .map.
$scope.appIds = $scope.applicationsHere.reduce(function(ids, obj){ if(obj.selected === true){ ids.push(obj.id); } return ids;
}, []);Just for the sake of clarity, here's the sample .reduce I used in the JSPerf test:
var things = [ {id: 1, selected: true}, {id: 2, selected: true}, {id: 3, selected: true}, {id: 4, selected: true}, {id: 5, selected: false}, {id: 6, selected: true}, {id: 7, selected: false}, {id: 8, selected: true}, {id: 9, selected: false}, {id: 10, selected: true}, ];
var ids = things.reduce((ids, thing) => { if (thing.selected) { ids.push(thing.id); } return ids;
}, []);
console.log(ids)EDIT 1
Note, As of 2/2018 Reduce + Push is fastest in Chrome and Edge, but slower than Filter + Map in Firefox
5You could use flatMap. It can filter and map in one.
$scope.appIds = $scope.applicationsHere.flatMap(obj => obj.selected ? obj.id : []) Here's some info if someone comes upon this in 2019.
I think reduce vs map + filter might be somewhat dependent on what you need to loop through. Not sure on this but reduce does seem to be slower.
One thing is for sure - if you're looking for performance improvements the way you write the code is extremely important!
Here a JS perf test that shows the massive improvements when typing out the code fully rather than checking for "falsey" values (e.g. if (string) {...}) or returning "falsey" values where a boolean is expected.
Hope this helps someone