34

I already have the pagination implemented. Now I want the pagination to be updated after filtering my results.

The form:

<input type="text" data-ng-model="search.name" data-ng-change="filter()"/>

The list:

<li data-ng-repeat="data in filtered = (list | filter:search) | filter:search | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">{{data.name}}</li>

The pagination:

<pagination data-boundary-links="true" data-num-pages="noOfPages" data-current-page="currentPage" max-size="maxSize"></pagination>

The controller:

$scope.filter = function() {
    window.setTimeout(function() { //wait for 'filtered' to be changed
        $scope.noOfPages = Math.ceil($scope.filtered.length/$scope.entryLimit);
        $scope.setPage = function(pageNo) {
            $scope.currentPage = pageNo;
        };
    }, 10);
};

My problem is, the pagination is just updated after clicking on a page number or after entering the next character into the input field. So it is update one step to late.

EDIT: I added the source to jsFiddle: http://jsfiddle.net/eqCWL/2/

Mischa
  • 1,073
  • 2
  • 13
  • 23

4 Answers4

36

@abject_error 's answer using $timeout does work. I edited your fiddle with his suggestion and made this jsFiddle

CAVEAT

I think the solution is indicative of a bigger problem in the form of a race condition!

jsFiddle using filterFilter and $watch

and that fiddle is the way around it for reals.

And here is the explanation

Your race condition is between handling the change of search and the availability of $scope.filtered.

I think the culprits to eliminate in order to resolve this race condition are:

ng-model="search" ng-change="filter()"

and

ng-repeat="data in filtered = (list | filter:search)......."

Using ng-change to fire off "filter()" to do the calculation of noOfPages but also depending on a change in search in order to create filtered. Doing it this way, ensures the filtered list cannot possibly be ready in time to calculate the number of pages, and that's why hobbling "filter()" by 10ms with a timeout gives you the illusion of a working program.

What you need is a way to "watch" search for changes, and then filter the list in a place where you have access to both creating $scope.filtered and calculating $scope.noOfPages. All in sequence, without a race.

Angular has that way! It is possible to use the filter filter in your controller as function very poorly named: filterFilter. Check this out in the Filters Guide - Using filters in controllers and services

Inject it into the controller.

function pageCtrl($scope, filterFilter) {
    // ...
}

Use it in a $watch function, documented in the scopes docs

$scope.$watch('search', function(term) {  
    // Create filtered 
    $scope.filtered = filterFilter($scope.list, term);  

    // Then calculate noOfPages
    $scope.noOfPages = Math.ceil($scope.filtered.length/$scope.entryLimit);  
})

Change the template to reflect our new way. No more in DOM filter, or ng-change

<input type="text" ng-model="search" placeholder="Search"/>

and

<li ng-repeat="data in filtered | startFrom:(currentPage-1)*entryLimit | limitTo:entryLimit">
    {{data.name}}
</li>
nackjicholson
  • 4,557
  • 4
  • 37
  • 35
  • This looks like a better sollution but if i change the ng-model="search" to ng-model"search.name" as follow it seems to break. What is causing this. I am asking this because i would like to be able to filter on multiple columns – Gabe Apr 06 '14 at 21:00
  • 3
    Change the watch expression to `search.name` as well. You may want to consider if $watchCollection is something to use in your case. Allows you to watch the properties of an object. – nackjicholson Apr 07 '14 at 05:02
  • I had already figured out the third parameter of the $watch function but i think the $watchCollection is even better. Thanks for that. – Gabe Apr 07 '14 at 10:08
  • I have my own pagination directive as I have to implement pagination and searching across multiple pages. In this case, how can i add the searching logic to my directive as ng-repeat refers to the controller. If i have to add this logic to the controller, then i have to copy paste the code for all of my pages. Any suggestions? – nathan1138 Apr 15 '14 at 19:29
  • I am in the same boat... I love this solution, but I am not sure how to implement it across my entire application without copy/pasting the code in every single controller – Matt Hintzke Aug 26 '14 at 19:00
  • @MattHintzke You might want to check this: https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination – AturSams Sep 10 '14 at 12:05
  • Guys, tag doesn't appear on my page. even thought ui-bootstrap is injected. Can enyone give me a hint? – OlehZiniak Mar 20 '16 at 14:10
  • Sorry, I don't understand where the filter filterFilter is defined. Can anyone explain this? – GBMan Apr 20 '16 at 08:28
4

You could simply use this directive instead:

https://github.com/michaelbromley/angularUtils/tree/master/src/directives/pagination

It offers pagination with a filter while keeping the code nice and clean.

Kudos to michaelbromley for the great code.

AturSams
  • 7,568
  • 18
  • 64
  • 98
3

Use $timeout instead of window.setTimeOut. $timeout is wrapped properly to work consistently in Angular.

Mohammad Faisal
  • 5,783
  • 15
  • 70
  • 117
abject_error
  • 2,858
  • 1
  • 19
  • 23
2

Use angular $scope.$watch

$scope.$watch('search', function(term) {
    $scope.filter = function() {
        $scope.noOfPages = Math.ceil($scope.filtered.length/$scope.entryLimit);
    }
});

Source ; http://jsfiddle.net/eqCWL/2/

Demo ; http://jsfiddle.net/eqCWL/192/

rfornal
  • 5,072
  • 5
  • 30
  • 42
sinHoot
  • 21
  • 1