Angular pagination not updating when bound list changes due to filtering on an input text box

Problem

Here's the scenario:

I am using an ASP.NET MVC site with Angular JS and Boostrap UI. I have a dynamic ul list populated by data fed through a controller call to AngularJS, filtering on that list through an input search box. The list is also controlled through pagination (UI Bootstrap control) that I've setup to show 10 results per page for the list of 100 or so items. This list is filtered as the user types in the search box, however I would like the pagination to update as well so consider the following example:

The list has 10 pages of items (100 items), the user types some text in the input search box which filters the list down to 20 or so items, so the pagination should be updated from 10 pages to two pages.

I figure there must be a $watch setup somewhere, perhaps on the list items after it has been filtered and then update the pagination page count, however I'm pretty new to AngularJS so can someone please explain to me how this could be done?

Thanks very much. I have posted my code below:

<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
    <ul>
        <li data-ng-repeat="deals in destinations | filter: cityName |
            startFrom:currentPage*pageSize | limitTo:pageSize">{{deals.Location}}</li>
    </ul>
    <br />
    <pagination rotate="true" num-pages="noOfPages" current-page="currentPage" 
                max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>

 var destApp = angular.module('dealsPage', ['ui.bootstrap', 'uiSlider']);
destApp.controller('DestinationController', function ($scope, $http) {
    $scope.destinations = {};
    $scope.currentPage = 1;
    $scope.pageSize = 10;

    $http.get('/Deals/GetDeals').success(function (data) {
        $scope.destinations = data;
        $scope.noOfPages = data.length / 10;
        $scope.maxSize = 5;
    });
});

destApp.filter('startFrom', function () {
    return function (input, start) {
        start = +start; //parse to int
        return input.slice(start);
    };
});
Problem courtesy of: Ben

Solution

Because your pagination is a combination of chained filters, Angular has no idea that when cityName changes, it should reset currentPage to 1. You'll need to handle that yourself with your own $watch.

You'll also want to adjust your startFrom filter to say (currentPage - 1) * pageSize, otherwise, you always start at page 2.

Once you get that going, you'll notice that your pagination is not accurate, because it's still based on destination.length, and not the filtered sub-set of destinations. For that, you're going to need to move your filtering logic from your view to your controller like so:

http://jsfiddle.net/jNYfd/

HTML

<div data-ng-app="dealsPage">
    <input type="text" data-ng-model="cityName" />
    <div data-ng-controller="DestinationController">
        <ul>
            <li data-ng-repeat="deals in filteredDestinations | startFrom:(currentPage - 1)*pageSize | limitTo:pageSize">{{deals.Location}}</li>
        </ul>
        <br />
        <pagination rotate="true" num-pages="noOfPages" current-page="currentPage" max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
    </div>

JavaScript

var destApp = angular.module('dealsPage', ['ui.bootstrap']);

destApp.controller('DestinationController', function ($scope, $http, $filter) {
    $scope.destinations = [];
    $scope.filteredDestinations = [];

    for (var i = 0; i < 1000; i += 1) {
        $scope.destinations.push({
            Location: 'city ' + (i + 1)
        });
    }

    $scope.pageSize = 10;
    $scope.maxSize = 5;

    $scope.$watch('cityName', function (newCityName) {
        $scope.currentPage = 1;
        $scope.filteredDestinations = $filter('filter')($scope.destinations, $scope.cityName);
        $scope.noOfPages = $scope.filteredDestinations.length / 10;
    });
});

destApp.filter('startFrom', function () {
    return function (input, start) {
        start = +start; //parse to int
        return input.slice(start);
    };
});
Solution courtesy of: Langdon

Discussion

The version shared on jsfiddle is compatible with ui-bootstrap 0.5.0 but from 0.6.0 onwards there have been breaking changes.

Here is a version that uses the following libraries:

  • angular 1.2.11
  • angular-ui-bootstrap 0.10.0
  • bootstrap 3.1.0

Here is a plunker for the same:

Angular UI Bootstrap Pagination

Discussion courtesy of: xcruciating

Hello I tried to hook this up with Firebase using Angular Fire and it only works after I type something in the search input. In the $scope.$watch method, I used Angular Fire's orderByPriorityFilter to convert the object to an array.

$scope.$watch('search', function(oldTerm, newTerm) {
  $scope.page = 1;
  // Use orderByPriorityFilter to convert Firebase Object into Array
  $scope.filtered = filterFilter(orderByPriorityFilter($scope.contacts), $scope.search);
  $scope.lastSearch.search = oldTerm;
  $scope.contactsCount = $scope.filtered.length;
});

Initial load doesn't load any contacts. It's only after I start typing in the input search field.

Discussion courtesy of: thienedits

This recipe can be found in it's original form on Stack Over Flow.