Martin Martin - 1 month ago 5
AngularJS Question

Filter filtered results function AngularJS ngrepeat

I want to filter filtered results.

I have the following JSON

$scope.JSON = [
{
"Animal": "Horse",
"Status": "awake",
"LastUpdate": {
"$date": 1473248184519
}
},
{
"Animal": "Rabbit",
"Status": "awake",
"LastUpdate": {
"$date": 1473248194240
}
},
{
"Animal": "Rabbit",
"Status": "eating",
"LastUpdate": {
"$date": 1473249639255
}
},
{
"Animal": "Horse",
"Status": "eating",
"LastUpdate": {
"$date": 1473249652549
}
},
{
"Animal": "Horse",
"Status": "sleeping",
"LastUpdate": {
"$date": 1473249656338
}
}
]


and the following filter function

$scope.filtering = filtering;

function filtering(animals){
var temp = [].concat(animals)
var animalsExisting = []; // To keep track of animals already added
return temp.reverse().filter(function(animal){
var notAdded = animalsExisting.indexOf(animal.Animal) === -1;
if(notAdded) {
animalsExisting.push(animal.Animal);
}
return notAdded;
})
}


See also this plunkr:
https://plnkr.co/edit/OQVjB47bpS9fKubO2lum?p=preview

How do I filter the returned Array notAdded with the Status, e.g. I want to show only the last & "eating" animals => result: Rabbit?

Answer

First option - Slow, but easy

Easiest way - using native Angular filter

<tr ng-repeat="roll in filtering(JSON) | filter : { Status: 'eating' }">


Second option - quick

Because either Angular will invoke filtering(JSON) on each $digest (which occures pretty often) it would be much better if you render a static list, not the return result of the function.

What I suggest is you rewrite your code to something along the lines

$scope.filters = {
    status: null
};

$scope.filteredAnimals = [];

$scope.getLastStatusesForEachAnimal = function() {
    var map = {};

    return [].concat($scope.JSON)
        .reverse()
        .filter(function(animal) {
            return !map[animal.Animal] ? (map[animal.Animal] = true) : false;
        });
};

$scope.filterAnimals = function() {
    $scope.filteredAnimals = $scope.getLastStatusesForEachAnimal()
        .filter(function(animal) {
            return $scope.filters.status ? animal.Status === $scope.filters.status : true;
        });
};

$scope.filterAnimals();


Third option - a tad slower, but more elegant

Write your own filter with caching, example

.filter('animalStatusFilter', function() {
    var cache = {
        params: {},
        result: null
    };

    return function(animals, status, onlyLast) {
        var params = {
            animals: animals,
            status: status,
            onlyLast: onlyLast
        };

        if (validateCache(params)) {
            return cache.result;
        }

        animals = onlyLast ? getLastStatusesForEachAnimal(animals) : animals;

        if (status) {
            animals = animals.filter(function(animal) {
                return status ? animal.Status === status : true;
            });
        }

        cache.params = params;
        cache.result = animals;

        return animals;
    };

    function validateCache(params) {
        return params.animals === cache.params.animals &&
            params.status === cache.params.status &&
            params.onlyLast === cache.params.onlyLast
    }

    function getLastStatusesForEachAnimal(animals) {
        var map = {};

        return [].concat(animals)
            .reverse()
            .filter(function(animal) {
                return !map[animal.Animal] ? (map[animal.Animal] = true) : false;
            });
    }
})
Comments