Jak filtrować wiele wartości (operacja OR) w angularJS


135

Chcę użyć filterw kątowym i chcę filtrować pod kątem wielu wartości, jeśli ma jedną z wartości, powinna zostać wyświetlona.

Mam na przykład taką strukturę:

Obiekt, moviektóry ma właściwość genresi chcę filtrować pod kątem Actioni Comedy.

Wiem, że mogę filter:({genres: 'Action'} || {genres: 'Comedy'}), ale co zrobić, jeśli chcę to dynamicznie filtrować. Na przykładfilter: variableX

Jak ustawić variableXw $scope, kiedy mam tablicę gatunków, które muszę filtrować?

Mógłbym skonstruować go jako ciąg, a następnie wykonać, eval()ale nie chcę używać eval () ...


16
jesteś pewien, że w filter:({genres: 'Action'} || {genres: 'Comedy'})ogóle działa? nie wchodzi angular 1.3.16.
twmulloy

4
to samo dla 1.4.8! to po prostu nie działa ^^
Alex,

Odpowiedzi:


87

Po prostu utworzyłbym niestandardowy filtr. Nie są takie trudne.

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

szablon:

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

Edytuj tutaj jest łącze: Tworzenie filtrów kątowych

AKTUALIZACJA : Oto skrzypce, które zawierają dokładne demo mojej sugestii.


więc na wyjściu zwracam obiekty filmowe, które chcę wyświetlić?
JustGoscha,

Przepraszam, że zajęło mi trochę czasu, zanim wróciłem. Zaktualizowałem odpowiedź, dodając skrzypce konkretnego przykładu.
Xesued

ok dzięki, w międzyczasie sam to wymyśliłem, ale na pewno komuś później pomoże :)
JustGoscha

Albo mam dwie wartości, takie jak aktywne i nieaktywne, a następnie jak mogę wyszukiwać osobno za pomocą tego filtru. nie chcemy tworzyć niestandardowego filtra.
VjyV

80

Do filtrowania można użyć funkcji kontrolera.

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
Oprócz oddzielenia obaw, czy jest jakiś powód (z punktu widzenia wydajności), aby tego uniknąć?
Sean Larkin

1
wersja javascript, jeśli inni jej szukają:$scope.filteredMovies = $filter('filter')($scope.movies, $scope.filterByGenres);
Evan

23

Tworzenie niestandardowego filtra może być tutaj przesadą, możesz po prostu przekazać niestandardowy komparator, jeśli masz wartości wielokrotności, takie jak:

$scope.selectedGenres = "Action, Drama"; 

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

następnie w filtrze:

filter:{name:selectedGenres}:containsComparator

1
Bardzo mi pomogło. Dzięki
Hristo Enev,

@chrismarx Co powinno ng-modelznajdować się w pliku widoku, aby połączyć się z opisanym filtrem? (Niestety, wciąż uczą tutaj) - lubię prostotę odpowiedź i staramy się działać, ale nie wiem, jak oznaczyć moje <input>„s ng-model. :)
twknab


18

Oto implementacja niestandardowego filtru, który będzie filtrował dane za pomocą tablicy wartości, będzie obsługiwał wiele obiektów kluczowych z zarówno tablicą, jak i pojedynczą wartością kluczy. Jak wspomniano wangularJS API AngularJS filter Doc obsługuje wiele filtrów kluczy z jedną wartością, ale poniżej niestandardowy filtr będzie obsługiwał tę samą funkcję co angularJS, a także obsługuje tablicę wartości i kombinację zarówno tablicy, jak i pojedynczej wartości kluczy. Poniżej znajduje się fragment kodu,

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

Stosowanie:

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

Oto przykład skrzypiec z implementacją powyższego niestandardowego filtra „filterMutiple” . ::: Przykład skrzypiec :::


1
Wydaje się, że masz właściwy zamiar, ale twój przykład nie działa zgodnie z oczekiwaniami. Dane wyjściowe w tabeli wydają się raczej niespójne.
Hultner,

Nie działa to w przypadku formatu JSON z obiektami zagnieżdżonymi. Byłoby idealnie, gdyby tak było. również odpala length property undefinedbłąd w konsoli.
SinSync

Działa dla mnie, ale zwraca wszystkie wyniki, jeśli nie znajdzie szukanego atrybutu na liście, jak mogę odwrócić to zachowanie i nie zwrócić wyniku, jeśli wartość nie istnieje na liście?
Zakaria Belghiti

@ZakariaBelghiti jeśli nie chcesz zwracać żadnych wyników, zmień kod: if (fData.length > 0){ this.filteredData = fData; } na tylko: this.filteredData = fData;
pconnor88

@Gerfried Myślę, że ta witryna faktycznie zeskrobuje Stackoverflow, więc ukradli jego odpowiedź, a nie odwrotnie
Chris

13

Jeśli chcesz filtrować według tablicy obiektów, możesz dać

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

Indywidualna właściwość zostanie przefiltrowana według konkretnego filtru podanego dla tej właściwości.

Ale jeśli chcesz filtrować według poszczególnych właściwości i filtrować globalnie dla wszystkich właściwości, możesz zrobić coś takiego.

<tr ng-repeat="supp in $data | filter : filterObject |  filter : search">
Gdzie „filterObject” to obiekt do wyszukiwania pojedynczej właściwości, a „Search” będzie przeszukiwać każdą właściwość globalnie.

~ Atul


To działało dobrze dla mnie i było lepszym rozwiązaniem niż inne, sugerując przeniesienie filtrowania na funkcję, ponieważ AngularJS dokonuje częściowych dopasowań tekstu podczas filtrowania na łańcuchach.
Snaver

9

Spędziłem nad tym trochę czasu i dzięki @chrismarx zobaczyłem, że domyślne ustawienie angulara filterFilterpozwala na przekazanie własnego komparatora. Oto edytowany komparator dla wielu wartości:

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

A jeśli chcemy zrobić niestandardowy filtr dla 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);
      };
    });

A potem możemy go używać w dowolnym miejscu:

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

Wreszcie mamy plunkr .

Uwaga: Oczekiwana tablica powinna zawierać tylko proste obiekty, takie jak String , Number itp.


Dzięki, pomogło mi. Aby sprawdzić pojedynczą wartość filterWithOr: {name: 'Jack'} Jeśli ktoś potrzebuje tego samego co ja ...
Juan

7

możesz użyć filtru searchField dla 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
Możesz także użyć parametru „where” w angular.filter <tr ng-repeat = "obj in collection | where: {id: 1}"> {{obj.name}} </tr>
hcarreras

1
searchzatrzymuje się tutaj.
Akash

1
Wypróbowałem to i skonfigurowałem zarówno moduł w mojej aplikacji kątowej, jak i skrypt w moim indeksowym HTML, ale nadal mogę wyszukiwać WSZYSTKIE pola, nie tylko to, które określiłem :(
twknab

7

Możesz także użyć, ngIfjeśli pozwala na to sytuacja:

<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
Ładne i proste rozwiązanie!
Leopoldo Sanczyk

7

Najszybszym rozwiązaniem, które znalazłem, jest użycie filterByfiltra z filtra kątowego , na przykład:

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

Zaletą jest to, że filtr kątowy jest dość popularną biblioteką (~ 2,6 tys. Gwiazdek na GitHub), która jest nadal aktywnie rozwijana i utrzymywana, więc dodanie jej do projektu jako zależności powinno być w porządku.


1
Zdecydowanie najlepsza odpowiedź. Dzięki.
Daniel Gruszczyk

4

Myślę, że właśnie tego szukasz:

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

1
Po prostu łączy tablice i nie zwraca unii.
muaaz

O ile nie zrozumiałem implementacji, nie rozumiem, jak to jest problem.
jKlaus

2

Spróbuj tego

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

Załóżmy, że masz dwie tablice, jedną dla filmów i jedną dla gatunku

Po prostu użyj filtra jako: filter:{genres: genres.type}

Tutaj gatunki będące tablicą i typem mają wartość dla gatunku


1

Napisałem to dla stringów ORAZ funkcjonalności (wiem, że to nie jest pytanie, ale szukałem i dostałem tutaj), może da się to rozszerzyć.

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

Stosowanie:

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


1

Miałem podobną sytuację. Pisanie niestandardowego filtra zadziałało dla mnie. Mam nadzieję że to pomoże!

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

Oto mój przykład, jak utworzyć filtr i dyrektywę dla tabeli jsfiddle

dyrektywy pobierz listę (dane) i utwórz tabelę z filtrami

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

z przyjemnością, jeśli ci pomogę


1

Za późno na dołączenie do imprezy, ale może to komuś pomoże:

Możemy to zrobić w dwóch krokach, najpierw filtrując według pierwszej właściwości, a następnie łącząc według drugiego filtru:

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

Zobacz działające skrzypce z filtrem wielu właściwości


0

OPCJA 1: Korzystanie z parametru porównawczego filtru zapewnianego przez 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);

OPCJA 2: Użycie Angular zapewniared negację filtru

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

najlepsza odpowiedź to:

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

5
Czy to nie tylko filtruje „Komedię”?
user1135469

2
Wypróbowałem to i filtruje tylko na „Komedię”, a nie na „Akcję”.
Ben
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.