Comment filtrer plusieurs valeurs (opération OR) dans angularJS


135

Je veux utiliser le filterdans angular et je veux filtrer plusieurs valeurs, s'il a l'une des valeurs, il devrait être affiché.

J'ai par exemple cette structure:

Un objet moviequi a la propriété genreset je veux filtrer pour Actionet Comedy.

Je sais que je peux faire filter:({genres: 'Action'} || {genres: 'Comedy'}), mais que faire si je veux le filtrer dynamiquement. Par exemplefilter: variableX

Comment définir variableXdans le $scope, quand j'ai un tableau des genres que je dois filtrer?

Je pourrais le construire sous forme de chaîne et ensuite faire un eval()mais je ne veux pas utiliser eval () ...


16
êtes-vous sûr que filter:({genres: 'Action'} || {genres: 'Comedy'})cela fonctionne même? ça ne rentre pas angular 1.3.16.
twmulloy

4
idem pour 1.4.8! ça ne marche pas ^^
Alex

Réponses:


87

Je créerais simplement un filtre personnalisé. Ils ne sont pas si difficiles.

angular.module('myFilters', []).
  filter('bygenre', function() {
    return function(movies,genres) {
      var out = [];
      // Filter logic here, adding matches to the out var.
      return out;
    }
  });

modèle:

<h1>Movies</h1>

<div ng-init="movies = [
          {title:'Man on the Moon', genre:'action'},
          {title:'Meet the Robinsons', genre:'family'},
          {title:'Sphere', genre:'action'}
       ];" />
<input type="checkbox" ng-model="genrefilters.action" />Action
<br />
<input type="checkbox" ng-model="genrefilters.family" />Family
<br />{{genrefilters.action}}::{{genrefilters.family}}
<ul>
    <li ng-repeat="movie in movies | bygenre:genrefilters">{{movie.title}}: {{movie.genre}}</li>
</ul>

Modifier voici le lien: Création de filtres angulaires

MISE À JOUR : Voici un violon qui a une démo exacte de ma suggestion.


alors dans le dehors je retourne les objets de film que je veux afficher?
JustGoscha

Désolé, il m'a fallu un certain temps pour revenir. J'ai mis à jour ma réponse avec un violon d'un exemple concret.
Xesued le

ok, merci, en attendant, je l'ai compris moi-même, mais cela aidera certainement quelqu'un plus tard :)
JustGoscha

Soit j'ai deux valeurs telles que active et inactive, alors comment puis-je rechercher séparément à l'aide de ce filtre. nous ne voulons pas créer de filtre personnalisé.
VjyV

80

Vous pouvez utiliser une fonction de contrôleur pour filtrer.

function MoviesCtrl($scope) {

    $scope.movies = [{name:'Shrek', genre:'Comedy'},
                     {name:'Die Hard', genre:'Action'},
                     {name:'The Godfather', genre:'Drama'}];

    $scope.selectedGenres = ['Action','Drama'];

    $scope.filterByGenres = function(movie) {
        return ($scope.selectedGenres.indexOf(movie.genre) !== -1);
    };

}

HTML:

<div ng-controller="MoviesCtrl">
    <ul>
        <li ng-repeat="movie in movies | filter:filterByGenres">
            {{ movie.name }} {{ movie.genre }}
        </li>
    </ul>
</div>

1
Outre la séparation des préoccupations, y a-t-il une raison (en termes de performances) que cela serait évité?
Sean Larkin

1
version javascript si d'autres la recherchent:$scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres);
Evan

23

Créer un filtre personnalisé peut être excessif ici, vous pouvez simplement passer un comparateur personnalisé, si vous avez des valeurs multiples comme:

$scope.selectedGenres = "Action, Drama"; 

$scope.containsComparator = function(expected, actual){  
  return actual.indexOf(expected) > -1;
};

puis dans le filtre:

filter:{name:selectedGenres}:containsComparator

1
M'a beaucoup aidé. Merci
Hristo Enev

@chrismarx Que serait le ng-modelfichier de vue à lier au filtre décrit? (désolé, apprendre encore ici) - Je aime la simplicité de votre réponse et d' essayer de le faire fonctionner , mais ne savez pas comment étiqueter mes <input>« s ng-model. :)
twknab


18

Voici l'implémentation du filtre personnalisé, qui filtrera les données à l'aide d'un tableau de valeurs.Il prendra en charge plusieurs objets clés avec à la fois un tableau et une valeur unique de clés. Comme mentionné dans l'API inangularJS AngularJS filter Doc prend en charge plusieurs filtres de clés avec une seule valeur, mais le filtre personnalisé ci-dessous prendra en charge la même fonctionnalité que angularJS et prend également en charge un tableau de valeurs et une combinaison de tableau et de valeur unique de clés.Veuillez trouver l'extrait de code ci-dessous,

myApp.filter('filterMultiple',['$filter',function ($filter) {
return function (items, keyObj) {
    var filterObj = {
        data:items,
        filteredData:[],
        applyFilter : function(obj,key){
            var fData = [];
            if (this.filteredData.length == 0)
                this.filteredData = this.data;
            if (obj){
                var fObj = {};
                if (!angular.isArray(obj)){
                    fObj[key] = obj;
                    fData = fData.concat($filter('filter')(this.filteredData,fObj));
                } else if (angular.isArray(obj)){
                    if (obj.length > 0){
                        for (var i=0;i<obj.length;i++){
                            if (angular.isDefined(obj[i])){
                                fObj[key] = obj[i];
                                fData = fData.concat($filter('filter')(this.filteredData,fObj));    
                            }
                        }

                    }
                }
                if (fData.length > 0){
                    this.filteredData = fData;
                }
            }
        }
    };
    if (keyObj){
        angular.forEach(keyObj,function(obj,key){
            filterObj.applyFilter(obj,key);
        });
    }
    return filterObj.filteredData;
}
}]);

Usage:

arrayOfObjectswithKeys | filterMultiple:{key1:['value1','value2','value3',...etc],key2:'value4',key3:[value5,value6,...etc]}

Voici un exemple de violon avec l'implémentation du filtre personnalisé "filterMutiple" ci-dessus . ::: Exemple de violon :::


1
Vous semblez avoir la bonne intention, mais votre exemple ne semble pas fonctionner comme prévu. La sortie du tableau semble plutôt incohérente.
Hultner

Cela ne fonctionne pas avec les JSON avec des objets imbriqués. Cela aurait été idéal si c'était le cas. il déclenche également une length property undefinederreur dans la console.
SinSync

Cela fonctionne pour moi mais il renvoie tous les résultats s'il ne trouve pas l'attribut recherché dans la liste, comment puis-je faire pour inverser ce comportement et ne renvoyer aucun résultat si la valeur n'existe pas dans la liste?
Zakaria Belghiti

@ZakariaBelghiti si vous ne voulez renvoyer aucun résultat, changez le code: if (fData.length > 0){ this.filteredData = fData; } en juste: this.filteredData = fData;
pconnor88

@Gerfried Je pense que ce site gratte en fait Stackoverflow, alors ils ont volé sa réponse, et non l'inverse
Chris

13

Si vous souhaitez filtrer sur un tableau d'objets, vous pouvez donner

filter:({genres: 'Action', key :value }.

La propriété individuelle sera filtrée par un filtre particulier donné pour cette propriété.

Mais si vous vouliez quelque chose comme filtrer par propriété individuelle et filtrer globalement pour toutes les propriétés, vous pouvez faire quelque chose comme ça.

<tr ng-repeat="supp in $data | filter : filterObject |  filter : search">
Où "filterObject" est un objet pour rechercher une propriété individuelle et "Search" cherchera dans chaque propriété globalement.

~ Atul


Cela a bien fonctionné pour moi et était une meilleure solution que d'autres suggérant de décharger le filtrage vers une fonction car AngularJS fait des correspondances partielles de texte lors du filtrage sur des chaînes.
Snaver

9

J'ai passé du temps dessus et grâce à @chrismarx, j'ai vu que le défaut angulaire filterFiltervous permet de passer votre propre comparateur. Voici le comparateur modifié pour plusieurs valeurs:

  function hasCustomToString(obj) {
        return angular.isFunction(obj.toString) && obj.toString !== Object.prototype.toString;
  }
  var comparator = function (actual, expected) {
    if (angular.isUndefined(actual)) {
      // No substring matching against `undefined`
      return false;
    }
    if ((actual === null) || (expected === null)) {
      // No substring matching against `null`; only match against `null`
      return actual === expected;
    }
    // I edited this to check if not array
    if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
      // Should not compare primitives against objects, unless they have custom `toString` method
      return false;
    }
    // This is where magic happens
    actual = angular.lowercase('' + actual);
    if (angular.isArray(expected)) {
      var match = false;
      expected.forEach(function (e) {
        e = angular.lowercase('' + e);
        if (actual.indexOf(e) !== -1) {
          match = true;
        }
      });
      return match;
    } else {
      expected = angular.lowercase('' + expected);
      return actual.indexOf(expected) !== -1;
    }
  };

Et si nous voulons créer un filtre personnalisé pour DRY:

angular.module('myApp')
    .filter('filterWithOr', function ($filter) {
      var comparator = function (actual, expected) {
        if (angular.isUndefined(actual)) {
          // No substring matching against `undefined`
          return false;
        }
        if ((actual === null) || (expected === null)) {
          // No substring matching against `null`; only match against `null`
          return actual === expected;
        }
        if ((angular.isObject(expected) && !angular.isArray(expected)) || (angular.isObject(actual) && !hasCustomToString(actual))) {
          // Should not compare primitives against objects, unless they have custom `toString` method
          return false;
        }
        console.log('ACTUAL EXPECTED')
        console.log(actual)
        console.log(expected)

        actual = angular.lowercase('' + actual);
        if (angular.isArray(expected)) {
          var match = false;
          expected.forEach(function (e) {
            console.log('forEach')
            console.log(e)
            e = angular.lowercase('' + e);
            if (actual.indexOf(e) !== -1) {
              match = true;
            }
          });
          return match;
        } else {
          expected = angular.lowercase('' + expected);
          return actual.indexOf(expected) !== -1;
        }
      };
      return function (array, expression) {
        return $filter('filter')(array, expression, comparator);
      };
    });

Et puis nous pouvons l'utiliser partout où nous voulons:

$scope.list=[
  {name:'Jack Bauer'},
  {name:'Chuck Norris'},
  {name:'Superman'},
  {name:'Batman'},
  {name:'Spiderman'},
  {name:'Hulk'}
];


<ul>
  <li ng-repeat="item in list | filterWithOr:{name:['Jack','Chuck']}">
    {{item.name}}
  </li>
</ul>

Enfin voici un plunkr .

Remarque: le tableau attendu ne doit contenir que des objets simples tels que String , Number, etc.


Merci cela m'a aidé. Pour vérifier une seule valeur filterWithOr: {name: 'Jack'} Si quelqu'un a besoin de la même chose que moi ...
Juan

7

vous pouvez utiliser le filtre searchField de angular.filter

JS:

$scope.users = [
 { first_name: 'Sharon', last_name: 'Melendez' },
 { first_name: 'Edmundo', last_name: 'Hepler' },
 { first_name: 'Marsha', last_name: 'Letourneau' }
];

HTML:

<input ng-model="search" placeholder="search by full name"/> 
<th ng-repeat="user in users | searchField: 'first_name': 'last_name' | filter: search">
  {{ user.first_name }} {{ user.last_name }}
</th>
<!-- so now you can search by full name -->

3
Vous pouvez également utiliser "where" de angular.filter <tr ng-repeat = "obj in collection | where: {id: 1}"> {{obj.name}} </tr>
hcarreras

1
les search arrêts ici.
Akash

1
J'ai essayé cela et j'ai configuré à la fois le module dans mon application angulaire et le script dans mon index HTML, mais je suis toujours en mesure de rechercher TOUS les champs, pas seulement celui que j'ai spécifié :(
twknab

7

Vous pouvez également utiliser ngIfsi la situation le permet:

<div ng-repeat="p in [
 { name: 'Justin' }, 
 { name: 'Jimi' }, 
 { name: 'Bob' }
 ]" ng-if="['Jimi', 'Bob'].indexOf(e.name) > -1">
 {{ p.name }} is cool
</div>

1
Solution sympa et simple!
Leopoldo Sanczyk

7

La solution la plus rapide que j'ai trouvée est d'utiliser le filterByfiltre de angular-filter , par exemple:

<input type="text" placeholder="Search by name or genre" ng-model="ctrl.search"/>   
<ul>
  <li ng-repeat="movie in ctrl.movies | filterBy: ['name', 'genre']: ctrl.search">
    {{movie.name}} ({{movie.genre}}) - {{movie.rating}}
  </li>
</ul>

L'avantage est que angular-filter est une bibliothèque assez populaire (~ 2,6k étoiles sur GitHub) qui est toujours activement développée et maintenue, donc ça devrait être bien de l'ajouter à votre projet en tant que dépendance.


1
De loin la meilleure réponse. Merci.
Daniel Gruszczyk

4

Je crois que c'est ce que vous recherchez:

<div>{{ (collection | fitler1:args) + (collection | filter2:args) }}</div>

1
Il concatène simplement les tableaux et ne renvoie pas d'union.
muaaz

À moins d'avoir mal compris la mise en œuvre, je ne vois pas en quoi cela pose un problème.
jKlaus

2

Veuillez essayer ceci

var m = angular.module('yourModuleName');
m.filter('advancefilter', ['$filter', function($filter){
    return function(data, text){
        var textArr = text.split(' ');
        angular.forEach(textArr, function(test){
            if(test){
                  data = $filter('filter')(data, test);
            }
        });
        return data;
    }
}]);

2

Supposons que vous ayez deux tableaux, un pour le film et un pour le genre

Utilisez simplement le filtre comme: filter:{genres: genres.type}

Ici, les genres étant le tableau et le type ont une valeur pour le genre


1

J'ai écrit ceci pour les chaînes ET la fonctionnalité (je sais que ce n'est pas la question mais je l'ai recherchée et je suis arrivée ici), peut-être qu'elle peut être étendue.

String.prototype.contains = function(str) {
  return this.indexOf(str) != -1;
};

String.prototype.containsAll = function(strArray) {
  for (var i = 0; i < strArray.length; i++) {
    if (!this.contains(strArray[i])) {
      return false;
    }
  }
  return true;
}

app.filter('filterMultiple', function() {
  return function(items, filterDict) {
    return items.filter(function(item) {
      for (filterKey in filterDict) {
        if (filterDict[filterKey] instanceof Array) {
          if (!item[filterKey].containsAll(filterDict[filterKey])) {
            return false;
          }
        } else {
          if (!item[filterKey].contains(filterDict[filterKey])) {
            return false;
          }
        }
      }
      return true;
    });  
  };
});

Usage:

<li ng-repeat="x in array | filterMultiple:{key1: value1, key2:[value21, value22]}">{{x.name}}</li>


1

J'ai eu une situation similaire. L'écriture d'un filtre personnalisé a fonctionné pour moi. J'espère que cela t'aides!

JS:

App.filter('searchMovies', function() {
    return function (items, letter) {
        var resulsts = [];
        var itemMatch = new RegExp(letter, 'i');
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if ( itemMatch.test(item.name) || itemMatch.test(item.genre)) {
                results.push(item);
            }
        }
        return results;
    };
});

HTML:

<div ng-controller="MoviesCtrl">
    <ul>
        <li ng-repeat="movie in movies | searchMovies:filterByGenres">
            {{ movie.name }} {{ movie.genre }}
        </li>
    </ul>
</div>

1

Voici mon exemple comment créer un filtre et une directive pour la table jsfiddle

directive get list (datas) et créer une table avec des filtres

<div ng-app="autoDrops" ng-controller="HomeController">
<div class="row">
    <div class="col-md-12">
        <h1>{{title}}</h1>
        <ng-Multiselect array-List="datas"></ng-Multiselect>
    </div>
</div>
</div>

mon plaisir si je vous aide


1

Il est trop tard pour rejoindre le groupe, mais cela peut peut-être aider quelqu'un:

Nous pouvons le faire en deux étapes, d'abord filtrer par première propriété puis concaténer par second filtre:

$scope.filterd = $filter('filter')($scope.empList, { dept: "account" });
$scope.filterd = $scope.filterd.concat($filter('filter')($scope.empList, { dept: "sales" }));  

Voir le violon de travail avec un filtre de propriétés multiples


0

OPTION 1: Utilisation du paramètre de comparateur de filtre fourni angulaire

// declaring a comparator method
$scope.filterBy = function(actual, expected) {
    return _.contains(expected, actual); // uses underscore library contains method
};

var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];

// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')(employees, {name: ['a','c']}, $scope.filterBy);

OPTION 2: Utilisation de la négation de filtre fournisseur angulaire

var employees = [{name: 'a'}, {name: 'b'}, {name: 'c'}, {name: 'd'}];

// filter employees with name matching with either 'a' or 'c'
var filteredEmployees = $filter('filter')($filter('filter')(employees, {name: '!d'}), {name: '!b'});

-3

Ma solution

ng-repeat="movie in movies | filter: {'Action'} + filter: {'Comedy}"

vérifié sur VS 2015, il n'obtient pas la syntaxe et cela ne fonctionne pas aussi
WholeLifeLearner

-15

la meilleure réponse est:

filter:({genres: 'Action', genres: 'Comedy'}

5
Cela ne filtre-t-il pas seulement sur «Comédie»?
user1135469

2
J'ai essayé cela et il ne filtre que sur «Comédie», pas sur «Action».
Ben
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.