Using array map to filter results with if conditional

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!


Solution 1:

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.

Solution 2:

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

Solution 3:

You could use flatMap. It can filter and map in one.

$scope.appIds = $scope.applicationsHere.flatMap(obj => obj.selected ? obj.id : [])

Solution 4:

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