Cómo filtrar valores múltiples (operación OR) en angularJS


135

Quiero usar el filteren angular y quiero filtrar por múltiples valores, si tiene uno de los valores, entonces debería mostrarse.

Tengo por ejemplo esta estructura:

Un objeto movieque tiene la propiedad genresy quiero filtrar por Actiony Comedy.

Sé que puedo hacer filter:({genres: 'Action'} || {genres: 'Comedy'}), pero qué hacer si quiero filtrarlo dinámicamente. P.ejfilter: variableX

¿Cómo configuro variableXel $scope, cuando tengo una variedad de géneros que tengo que filtrar?

Podría construirlo como una cadena y luego hacer un eval()pero no quiero usar eval () ...


16
¿estás seguro de que filter:({genres: 'Action'} || {genres: 'Comedy'})incluso funciona? no entra angular 1.3.16.
twmulloy

44
¡Lo mismo para 1.4.8! simplemente no funciona ^^
Alex

Respuestas:


87

Simplemente crearía un filtro personalizado. No son tan difíciles.

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

modelo:

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

Editar aquí está el enlace: Crear filtros angulares

ACTUALIZACIÓN : Aquí hay un violín que tiene una demostración exacta de mi sugerencia.


Entonces, ¿en el exterior devuelvo los objetos de película que quiero mostrar?
JustGoscha

Lo siento, me tomó un tiempo regresar. Actualicé mi respuesta con un violín de un ejemplo concreto.
Xesued

ok, gracias, mientras tanto lo descubrí yo mismo, pero seguramente ayudará a alguien más tarde :)
JustGoscha

O tengo dos valores como activo e inactivo, entonces, ¿cómo puedo buscar por separado usando este filtro? No queremos hacer un filtro personalizado.
VjyV

80

Puede usar una función de controlador para filtrar.

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
Además de la separación de las preocupaciones, ¿hay alguna razón (en cuanto al rendimiento) para evitar esto?
Sean Larkin

1
versión de JavaScript si otros lo están buscando:$scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres);
Evan

23

Crear un filtro personalizado puede ser excesivo aquí, solo puede pasar un comparador personalizado, si tiene valores múltiples como:

$scope.selectedGenres = "Action, Drama"; 

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

luego en el filtro:

filter:{name:selectedGenres}:containsComparator

1
Me ayudo mucho. Gracias
Hristo Enev

@chrismarx ¿Cuál sería el ng-modelarchivo de vista para vincular con el filtro descrito? (lo siento, todavía está aprendiendo aquí) - me gusta la simplicidad de su respuesta y tratando de conseguir que funcione, pero no está seguro de cómo etiquetar mis <input>'s ng-model. :)
twknab


18

Aquí está la implementación de un filtro personalizado, que filtrará los datos utilizando una matriz de valores. Admitirá objetos de clave múltiple con matriz y valor único de claves. Como se mencionó en la API deangularJS, el filtro AngularJS Doc admite un filtro de clave múltiple con un solo valor, pero el filtro personalizado a continuación admitirá la misma característica que angularJS y también admite una matriz de valores y una combinación de la matriz y el valor único de las claves. Busque el fragmento de código a continuación,

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

Uso:

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

Aquí hay un ejemplo de violín con la implementación del filtro personalizado "filterMutiple" anterior . ::: Ejemplo de violín :::


1
Parece que tiene la intención correcta, pero su ejemplo no parece funcionar como se esperaba. El resultado en la tabla parece bastante inconsistente.
Hultner

Esto no funciona con JSON con objetos anidados. Hubiera sido ideal si así fuera. También dispara un length property undefinederror en la consola.
SinSync

Me está funcionando pero devuelve todos los resultados si no encuentra el atributo buscado en la lista, ¿cómo puedo hacer para invertir este comportamiento y no devolver ningún resultado si el valor no existe en la lista?
Zakaria Belghiti

@ZakariaBelghiti si no desea devolver ningún resultado, cambie el código: if (fData.length > 0){ this.filteredData = fData; } a solo: this.filteredData = fData;
pconnor88

@Gerfried Creo que ese sitio en realidad raspa Stackoverflow, por lo que robaron su respuesta, no al revés
Chris

13

Si desea filtrar en la matriz de objetos, puede dar

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

La propiedad individual será filtrada por un filtro particular dado para esa propiedad.

Pero si desea filtrar algo por Propiedad individual y filtrar globalmente todas las propiedades, puede hacer algo como esto.

<tr ng-repeat="supp in $data | filter : filterObject |  filter : search">
Donde "filterObject" es un objeto para buscar una propiedad individual y "Search" buscará en cada propiedad a nivel mundial.

~ Atul


Esto funcionó bien para mí y fue una solución mejor que otras que sugirieron descargar el filtrado a una función, ya que AngularJS hace coincidencias de texto parcial cuando se filtra en cadenas.
Snaver

9

He dedicado algo de tiempo y gracias a @chrismarx, vi que el valor predeterminado de angular le filterFilterpermite pasar su propio comparador. Aquí está el comparador editado para múltiples valores:

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

Y si queremos hacer un filtro personalizado para 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);
      };
    });

Y luego podemos usarlo en cualquier lugar que queramos:

$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>

Finalmente aquí hay un plunkr .

Nota: La matriz esperada solo debe contener objetos simples como String , Number , etc.


Gracias, esto me ayudó. Para buscar un valor único filterWithOr: {name: 'Jack'} Si alguien necesita lo mismo que yo ...
Juan

7

puedes usar el filtro 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
También puede usar "where" de angular.filter <tr ng-repeat = "obj en la colección | where: {id: 1}"> {{obj.name}} </tr>
hcarreras

1
Las searchparadas aquí.
Akash

1
Intenté esto y configuré tanto el módulo en mi aplicación angular como el script en mi índice HTML, pero aún puedo buscar TODOS los campos, no solo el que especifiqué :(
twknab

7

También puede usar ngIfsi la situación lo permite:

<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
¡Una solución agradable y simple!
Leopoldo Sanczyk

7

La solución más rápida que he encontrado es utilizar el filterByfiltro de filtro angular , por ejemplo:

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

Lo bueno es que el filtro angular es una biblioteca bastante popular (~ 2.6k estrellas en GitHub) que todavía se desarrolla y mantiene activamente, por lo que debería estar bien agregarlo a su proyecto como una dependencia.


1
Con mucho, la mejor respuesta. Gracias.
Daniel Gruszczyk

4

Creo que esto es lo que estás buscando:

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

1
Simplemente concatena las matrices y no devuelve una unión.
muaaz

A menos que haya entendido mal la implementación, no veo cómo eso es un problema.
jKlaus

2

Por favor intente esto

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

Supongamos que tiene dos conjuntos, uno para película y otro para género.

Solo usa el filtro como: filter:{genres: genres.type}

Aquí los géneros son la matriz y el tipo tiene valor para el género


1

Escribí esto para cadenas y funcionalidad (sé que no es la pregunta, pero lo busqué y llegué aquí), tal vez pueda expandirse.

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

Uso:

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


1

Tuve una situación similar. Escribir filtros personalizados funcionó para mí. ¡Espero que esto ayude!

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

Aquí está mi ejemplo de cómo crear filtro y directiva para la tabla jsfiddle

directiva obtener lista (datos) y crear tabla con filtros

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

un placer si te ayudo


1

Demasiado tarde para unirse a la fiesta, pero puede ser que pueda ayudar a alguien:

Podemos hacerlo en dos pasos, primer filtro por primera propiedad y luego concatenar por segundo filtro:

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

Vea el violín de trabajo con filtro de propiedades múltiples


0

OPCIÓN 1: Uso del parámetro de comparación de filtro proporcionado angular

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

OPCIÓN 2: Uso de negación de filtro proporcionado angular

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


-15

la mejor respuesta es:

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

55
¿Esto no solo se filtra en 'Comedia'?
user1135469

2
Intenté esto y solo se filtra en 'Comedia', no en 'Acción'.
Ben
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.