Como filtrar vários valores (operação OR) no angularJS

135

Eu quero usar o filterin angular e quero filtrar por vários valores, se ele tiver um dos valores, ele deverá ser exibido.

Eu tenho por exemplo esta estrutura:

Um objeto movieque possui a propriedade genrese desejo filtrar por Actione Comedy.

Sei que posso fazer filter:({genres: 'Action'} || {genres: 'Comedy'}), mas o que fazer se quiser filtrá-lo dinamicamente. Por exemplofilter: variableX

Como faço para configurar variableXno $scope, quando eu tenho uma variedade de gêneros que tenho de filtro?

Eu poderia construí-lo como uma string e, em seguida, fazer um, eval()mas não quero usar eval () ...

JustGoscha
fonte
16
você tem certeza que isso filter:({genres: 'Action'} || {genres: 'Comedy'})funciona? não entra angular 1.3.16.
twmulloy
4
mesmo para 1.4.8! é só não funciona ^^
Alex

Respostas:

87

Gostaria apenas de criar um filtro personalizado. Eles não são tão difíceis.

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>

Edite aqui o link: Criando filtros angulares

ATUALIZAÇÃO : Aqui está um violino que tem uma demonstração exata da minha sugestão.

Xesued
fonte
então na saída eu devolvo os objetos do filme que eu quero exibir?
fácil
Desculpe, demorei um pouco para voltar. Atualizei minha resposta com um violino de exemplo concreto.
Xesued
ok, obrigado, nesse meio tempo eu descobri-lo eu mesmo, mas sure ajuda alguém mais tarde :)
JustGoscha
Ou eu tenho dois valores, como ativo e inativo, como posso pesquisar separadamente usando esse filtro. não queremos criar filtros personalizados.
VjyV
80

Você pode usar uma função 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>
jlareau
fonte
1
Além da separação de preocupações, existe alguma razão (em termos de desempenho) para que isso seja evitado?
Sean Larkin
1
versão javascript, se outros estiverem procurando:$scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres);
Evan
23

Criar um filtro personalizado pode ser um exagero aqui, basta passar um comparador personalizado, se você tiver os valores múltiplos como:

$scope.selectedGenres = "Action, Drama"; 

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

depois no filtro:

filter:{name:selectedGenres}:containsComparator
chrismarx
fonte
1
Me ajudou muito. Obrigado
Hristo Enev
@chrismarx Qual seria o ng-modelarquivo de visualização para vincular ao filtro descrito? (desculpe, ainda está aprendendo aqui) - Eu gosto da simplicidade da sua resposta e tentar obtê-lo funcionar, mas não tenho certeza como rotular meus <input>'s ng-model. :)
twknab
você precisa ler este - docs.angularjs.org/api/ng/filter/filter
chrismarx
18

Aqui está a implementação do filtro personalizado, que filtrará os dados usando a matriz de valores. Ele oferecerá suporte a vários objetos-chave com matriz e valor único de chaves. Conforme mencionado na API inangularJS, o filtro AngularJS Doc suporta vários filtros de chave com valor único, mas o filtro personalizado abaixo suporta o mesmo recurso que angularJS e também oferece matriz de valores e combinações de matriz e valor único de chaves.

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

Aqui está um exemplo simples com a implementação do filtro personalizado "filterMutiple" acima . ::: Exemplo de violino :::

Nirmal Kumar VeluPillai
fonte
1
Você parece ter a intenção correta, mas seu exemplo não parece funcionar conforme o esperado. A saída na tabela parece bastante inconsistente.
Hultner 16/09
Isso não funciona com JSONs com objetos aninhados. Teria sido ideal se tivesse. Também dispara um length property undefinederro no console.
SinSync 23/01
Está funcionando para mim, mas retorna todos os resultados se não encontrar o atributo pesquisado na lista. Como posso inverter esse comportamento e não retornar nenhum resultado se o valor não existir na lista?
Zakaria Belghiti
@ZakariaBelghiti se você quiser retornar nenhum resultado, em seguida, alterar o código: if (fData.length > 0){ this.filteredData = fData; } apenas: this.filteredData = fData;
pconnor88
@ Gerfried Eu acho que esse site realmente raspa o Stackoverflow, então eles roubaram a resposta dele, e não o contrário
Chris
13

Se você deseja filtrar a Matriz de objetos, pode dar

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

A propriedade individual será filtrada por um filtro específico fornecido para essa propriedade.

Mas se você quiser algo como filtrar por Propriedade individual e filtrar globalmente para todas as propriedades, poderá fazer algo assim.

<tr ng-repeat="supp in $data | filter : filterObject |  filter : search">
Onde "filterObject" é um objeto para pesquisar uma propriedade individual e "Search" pesquisará em todas as propriedades globalmente.

~ Atul

user1941574
fonte
Isso funcionou bem para mim e foi uma solução melhor do que outras sugerindo descarregar a filtragem para uma função, pois o AngularJS faz correspondências parciais de texto ao filtrar em strings.
Snaver
9

Passei algum tempo nisso e, graças a @chrismarx, vi que o padrão do angular filterFilterpermite que você passe seu próprio comparador. Aqui está o comparador editado para vários 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;
    }
  };

E se quisermos criar um 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);
      };
    });

E então podemos usá-lo onde quisermos:

$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, aqui está um plunkr .

Nota: A matriz esperada deve conter apenas objetos simples como String , Number etc.

s.alem
fonte
Graças isso me ajudou. Para verificar se há uma única filterWithOr valor: {name: 'Jack'} Se alguém necessidade o mesmo que eu ...
Juan
7

você pode usar o filtro searchField do 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 -->
a8m
fonte
3
Você também pode usar "onde" da angular.filter <tr ng-repeat = "obj na coleção | onde: {id: 1}"> {{obj.name}} </ tr>
hcarreras
1
as searchparadas aqui.
Akash
1
Eu tentei isso e configuração tanto o módulo em meu aplicativo angular eo script em meu índice de HTML, mas eu ainda sou capaz de procurar todos os campos, não apenas o que eu especificado :(
twknab
7

Você também pode usar ngIfse a situação permitir:

<div ng-repeat="p in [
 { name: 'Justin' }, 
 { name: 'Jimi' }, 
 { name: 'Bob' }
 ]" ng-if="['Jimi', 'Bob'].indexOf(e.name) > -1">
 {{ p.name }} is cool
</div>
cyberwombat
fonte
1
Solução agradável e simples!
Leopoldo Sanczyk
7

A solução mais rápida que encontrei é usar o filterByfiltro do filtro angular , por exemplo:

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

O lado positivo é que o filtro angular é uma biblioteca bastante popular (~ 2.6k estrelas no GitHub) que ainda é ativamente desenvolvida e mantida; portanto, convém adicioná-lo ao seu projeto como uma dependência.

Priidu Neemre
fonte
1
De longe a melhor resposta. Obrigado.
Daniel Gruszczyk
4

Acredito que é isso que você está procurando:

<div>{{ (collection | fitler1:args) + (collection | filter2:args) }}</div>
jKlaus
fonte
1
Apenas concatena as matrizes e não retorna um sindicato.
muaaz
A menos que eu tenha entendido mal a implementação, não vejo como isso é um problema.
Jklaus
2

Por favor tente isto

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

Vamos supor que você tenha duas matrizes, uma para filme e outra para gênero

Basta usar o filtro como: filter:{genres: genres.type}

Aqui os gêneros, sendo o array e o tipo, têm valor para o gênero

Amit Yadav
fonte
1

Eu escrevi isso para cadeias E funcionalidade (sei que não é a pergunta, mas procurei e cheguei aqui), talvez possa ser expandido.

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>
Uri
fonte
1

Eu tive situação semelhante. Escrever filtro personalizado funcionou para mim. Espero que isto ajude!

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>
hari nair
fonte
1

Aqui está meu exemplo de como criar filtro e diretiva para a tabela jsfiddle

diretiva obter lista (dados) e criar tabela com 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>

meu prazer se eu te ajudar

Jasmin Kurtic
fonte
1

Tarde demais para participar da festa, mas pode ser que ajude alguém:

Podemos fazer isso em duas etapas, primeiro filtrar pela primeira propriedade e concatenar pelo segundo filtro:

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

Veja o violino de trabalho com filtro de várias propriedades

Ali Adravi
fonte
0

OPÇÃO 1: Usando o parâmetro comparador de filtro fornecido 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);

OPÇÃO 2: Usando negação de filtro fornecida 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'});
Deepak Acharya
fonte
-3

Minha solução

ng-repeat="movie in movies | filter: {'Action'} + filter: {'Comedy}"
Wilson Di Mateo
fonte
verificado no VS 2015, ele não obtém a sintaxe e também não funciona
WholeLifeLearner
-15

a melhor resposta é:

filter:({genres: 'Action', genres: 'Comedy'}
Chinaxing
fonte
5
Isso não apenas filtra 'Comédia'?
User1135469
2
Eu tentei isso e filtra apenas 'Comédia', não 'Ação'.
Ben