user2727195
user2727195

Reputation: 7330

angular multiple filters for ng-repeat items

I've an ng-repeat list with multiple filters.

    <div class="search pull-right">
        <div class="magnify"><img src="img/magnify.png" ng-click="magnify()"></div>
    </div>
    <div class="search-box" ng-show="showSearch">
        <input type="search" id="searchField" name="" value="" placeholder="Search" class="textfield" ng-model="search.firstName" /> 
    </div>

List 1 (attendee.chat is not 0) gets affect by search TextField

        <ul class="table-view">      
            <li class="table-view-cell attendee" ng-repeat="attendee in attendees | filter:search | filter:attendee.chat">
                <h2 class="name">{{attendee.firstName}} {{attendee.lastName}}</h2>
                <span class="job">{{attendee.title}}</span>
                <span class="company">{{attendee.company}}</span>
            </li>
        </ul>

List 2 (attendee.chat is 0) but also gets affect by search TextField

<ul class="table-view">      
                <li class="table-view-cell attendee" ng-repeat="attendee in attendees | filter:search | filter:attendee.chat">
                    <h2 class="name">{{attendee.firstName}} {{attendee.lastName}}</h2>
                    <span class="job">{{attendee.title}}</span>
                    <span class="company">{{attendee.company}}</span>
                </li>
            </ul>

any DRY approach would also be appreciated since I've two similar templates up there. (difference is attendee.chat it can be 0 or any positive number)

Upvotes: 1

Views: 1126

Answers (1)

GregL
GregL

Reputation: 38103

Approach 1

To make the markup more DRY, you could create a directive:

app.directive('attendeeDetails', function () {
    return {
        restrict: 'E',
        replace: true, // even though this option is deprecated
        scope: {
            attendee: '&' // don't need 2-way binding
        }
        template: '<li class="table-view-cell attendee">' +
            '<h2 class="name">{{attendee().firstName}} {{attendee().lastName}}</h2>' + 
            '<span class="job">{{attendee().title}}</span>' +
            '<span class="company">{{attendee().company}}</span>' +
        '</li>'
     };
 });

Then just change your markup to use it:

List 1:

<ul class="table-view">      
     <attendee-details attendee="attendee" ng-repeat="attendee in attendees | filter:list1Filter"></attendee-details>
</ul>

List 2:

<ul class="table-view">      
     <attendee-details attendee="attendee" ng-repeat="attendee in attendees | filter:list2Filter"></attendee-details>
</ul>

Then write the two simple filter functions in the controller:

$scope.list1Filter = function (attendee) {
    return matchesSearch(attendee) && attendee.chat > 0;
};

$scope.list2Filter = function (attendee) {
    return matchesSearch(attendee) && attendee.chat === 0;
};

function matchesSearch(attendee) {
    return attendee.firstName.toLocaleLowerCase().indexOf($scope.search.firstName.toLocaleLowerCase()) >= 0;
}

(I am using .toLocaleLowerCase() instead of .toLowerCase() because that has become my preference so it will cater for any locale specific cases. Saves me effort if I ever had to port my code to work in Turkey, for instance.)

Approach 2

You could also use templates together with ng-include like so:

<script type="text/ng-template" id="/attendee-details.html">
     <h2 class="name">{{attendee.firstName}} {{attendee.lastName}}</h2>
     <span class="job">{{attendee.title}}</span>
     <span class="company">{{attendee.company}}</span>
</script>

Then your markup would become as follows:

List 1:

<ul class="table-view">      
     <li class="table-view-cell attendee" 
         ng-include="'/attendee-details.html'"
         ng-repeat="attendee in attendees | filter:list1Filter">
     </li>
</ul>

List 2:

<ul class="table-view">      
     <li class="table-view-cell attendee" 
         ng-include="'/attendee-details.html'"
         ng-repeat="attendee in attendees | filter:list2Filter">
     </li>
</ul>

Note the distinct disadvantage with this approach is if you want to create valid HTML markup is that you cannot put the <li> inside the template, since the contents of the template will be inserted inside the node with ng-include on it. Since valid HTML can't have anything inside a <ul> other than <li>s, you need to duplicate the <li> and its classes for both lists.

Why Not Multiple Filters?

The reason I chose to go with the controller function for the filter, rather than using multiple filters inline in the expression is that your criteria for list 1 of showing all elements with attendee.chat > 0 is not possible to express using any of the builtin filters as far as I can see. You cannot specify an expression for the thing to match against.

You could use multiple filters for List 2, though, since you are only checking for a single value for attendee.chat (0).

You could do ng-repeat="attendee in attendees | filter:{firstName:search.firstName,chat:0}", or if you had to have multiple filters instead (which will be slower, so I don't know why you would), you could do ng-repeat="attendee in attendees | filter:search | filter:{chat:0}".

Upvotes: 1

Related Questions