Custom filter with AngularJS ngTable
Asked Answered
A

3

12

I'm trying to construct a table using ngTable, but with different custom filtering than described in the example from the ngTable page.

I want filtering in place, but I don't want ngTable to render the filter selectors. I want to render them myself (above the table), and then reference them in my "getData()" method.

The example referred to earlier doesn't explain how any of that machinery works. I have no idea exactly what needs to be specified in the "filter" property in each "td" element. I understand the basic syntax of the AngularJS $filter function, but I'm not clear on what ngTable is doing with this. From the one example, it looks like I can only do "equals" checking, which would only select rows where the associated column value is equal to the filter value. That isn't quite what I need.

My table has several columns. Two of them are called "key" and "failed", being string and boolean respectively. When I'm rendering these filter fields above the table, I need a custom label for the "failed" filter. Filtering for the "key" column should match the filter value with any substring of the "key" values. For instance, if I have key values of "abc", "abac", and "def", a filter value of "a" will result in the first two entries showing, and not showing the "def" entry.

Update:

Related to this, I wish I could figure out how to do something like this:

Let's say I have a ngRepeat expression in my table element like this, using "standard" angularjs filters:

"item in $data | customfilter:param | anothercustomfilter:param"

We know that this doesn't quite work, as those filters will only apply to one page slice obtained from the "getData()" method. What I'd really like to be able to do in my "getData()" method is simply access the entire filter chain, including the parameter expressions, and simply pass a different array into it, being the entire original data list, not just the page slice.

At the same time, I'd need to be able to "turn off" the filtering angularjs is doing by itself, by executing that filter chain in its normal processing.

This sounds difficult, but I find the current API requires a lot of coupling between the html and the javascript. It would be nice if the html could specify the desired filtering, and the javascript would just use that entire filter chain, but use it on the entire data list, not just the page slice.

Update:

Here's a relevant excerpt from my HTML:

<label for="keysFilter">Filter Keys:</label>
<input id="keysFilter" type="text" ng-model="keysFilter"/>
<label for="showOnlyFailed">Show only queries that failed?</label>
<input id="showOnlyFailed" type="checkbox" ng-model="showOnlyFailed"/>
<table ng-table="tableParams" table-pagination="custom/pages" class="table">
<tr ng-repeat="queryInfo in $data"> <!--  | filterFailed:showOnlyFailed | filterMatchingKeys:keysFilter -->

Here's my tableParams code:

$scope.tableParams  = new ngTableParams({
    page: 1,
    count: 10,
    sorting: {
        lastRun: 'desc'
    }
},
{
    debugMode: true,
    total:  $scope.completedQueries.length,
    getData:    function($defer, params) {
        var orderedData = params.sorting() ?
                $filter('orderBy')($scope.completedQueries, params.orderBy()) :
                data;
        orderedData = $filter('filterFailed')(orderedData, $scope.showOnlyFailed);
        orderedData = $filter('filterMatchingKeys')(orderedData, $scope.keysFilter);

        params.total(orderedData.length);
        $defer.resolve(orderedData.slice((params.page() - 1) * params.count(),
                                                     params.page() * params.count()));
    }
});

Note that I used have this ngTable not using the "$data" list, and just iterating through my "completedQueries" list. When it worked like that, the list would immediately change when I clicked on the "Show only queries that failed" checkbox, or entered text in the "keysFilter" input field.

However, now that I'm using the "$data" list, nothing happens when I change either of those fields. In fact, I even added $watch-es for both of those fields, and neither of them fire. However, when I make changes to either of those fields, I know the table data is being reevaluated, because two of the columns have data that are expected to be millis value, and I have a custom filter on those columns that translate the value to a "time ago" english expression, like "30 seconds ago", or "2 minutes ago", and each time I change one of those input fields, I see those expressions in the table change, but it still doesn't do the proper filtering.

If it matters, here are the $watch-es that I added to my scope. These never appear to fire:

    $scope.$watch("showOnlyFailed", function() {
    $scope.tableParams.reload();
});

$scope.$watch("keysFilter", function() {
    $scope.tableParams.reload();
});

Note that when I have these commented in, I see the following error after I hit my "getData()" method:

Error: settings.$scope is null
@http://localhost:8000/js/diag/libs/ng-table.src.js:411
qFactory/defer/deferred.promise.then/wrappedCallback@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:11046
qFactory/ref/<.then/<@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:11132
Scope.prototype.$eval@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:12075
Scope.prototype.$digest@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:11903
Scope.prototype.$apply@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:12179
bootstrap/doBootstrap/<@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:1341
invoke@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:3762
bootstrap/doBootstrap@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:1340
bootstrap@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:1353
angularInit@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:1301
@http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js:21048
n.Callbacks/j@http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js:2
n.Callbacks/k.fireWith@http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js:2
.ready@http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js:2
K@http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js:2
http://ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.js
Line 9509

This is the relevant block of code:

            $defer.promise.then(function (data) {
            settings.$loading = false;
            log('ngTable: current scope', settings.$scope);
            if (settings.groupBy) {
                self.data = settings.$scope.$groups = data;
            } else {
                self.data = settings.$scope.$data = data; // line 411
            }
            settings.$scope.pages = self.generatePagesArray(self.page(), self.total(), self.count());
        });

Update:

Here is my plunkr that demonstrates that changing the external filter fields doesn't work. I also have two $watch-es commented out to try to rectify this. When I comment those in, I get an error within ng-table, complaining of a null scope.

Update:

I tried adding the "newvalue, oldvalue" parameters to my $watch-es (I updated the plunkr). Now changes to the fields are causing the table to update. Unfortunately, I still get that stack trace on line 411 of ng-table.

Asarum answered 17/3, 2014 at 16:2 Comment(3)
I try your plunkr and it's work like a charm for me.Karilynn
I am using Smart Table May it will help you.Fielding
<input name="name" ng-model="tableParams.$params.filter" + "<append name that you want>" type="search">Beetner
N
8

You don't need the watches nor the custom filters you created. Actually angular's 'filter' filter is quite powerful.

You just need to create an object that keeps track of your filter values with members that match your items fields. Something like this.

$scope.filter = {
    key: undefined,
    failed: undefined
}

then you can take it back inside the getData callback using params.filter(). I have updated your plunker here. You can also check the sample below:

var app = angular.module('main', ['ngTable']);

app.controller('MainCtrl', function($scope, $http, $filter, ngTableParams) {

    $scope.completedQueries = [{"key":"abc000","lastRun":123,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc001","lastRun":1234,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc002","lastRun":111111111,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":true},{"key":"abc003","lastRun":123,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc004","lastRun":1234,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":true},{"key":"abc005","lastRun":111111111,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc006","lastRun":123,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc007","lastRun":1234,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc008","lastRun":111111111,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc009","lastRun":123,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc010","lastRun":1234,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc011","lastRun":111111111,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc012","lastRun":123,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":true},{"key":"abc013","lastRun":1234,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc014","lastRun":111111111,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc015","lastRun":123,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":true},{"key":"abc016","lastRun":1234,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false},{"key":"abc017","lastRun":111111111,"lastSuccessfulTime":9999,"elapsedTime":456,"rows":10,"failed":false}];
    $scope.filter = {
        key: undefined,
        failed: undefined
    };
    $scope.tableParams =  new ngTableParams({
        page: 1,
        count: 10,
        filter: $scope.filter
    }, {
        debugMode: true,
        total: $scope.completedQueries.length,
        getData: function($defer, params) {
            var orderedData = params.sorting() ? $filter('orderBy')($scope.completedQueries, params.orderBy()) : data;
            orderedData	= $filter('filter')(orderedData, params.filter());
            params.total(orderedData.length);
            $defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
        }
    });
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/ng-table/0.3.3/ng-table.min.css" rel="stylesheet"/>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ng-table/0.3.3/ng-table.min.js"></script>
<div  ng-app="main"  ng-controller="MainCtrl">
  <label for="keysFilter">Filter Keys:</label>
  <input id="keysFilter" type="text" ng-model="filter.key"/>
  <label for="showOnlyFailed">Show only queries that failed?</label>
  <input id="showOnlyFailed" type="checkbox" ng-model="filter.failed"/>
  <br/>
  <table ng-table="tableParams" class="table">
    <tr ng-repeat="queryInfo in $data">
        <td data-title="'Key'" sortable="'key'">{{queryInfo.key}}</td>
        <td data-title="'Last Run'" sortable="'lastRun'">{{queryInfo.lastRun}}</td>
        <td data-title="'Last Successful Run'" sortable="'lastSuccessfulRun'">{{queryInfo.lastSuccessfulRun}}</td>
        <td data-title="'Elapsed Time'" sortable="'elapsedTime'">{{queryInfo.elapsedTime}} ms</td>
        <td data-title="'Rows'" sortable="'rows'">{{queryInfo.rows}}</td>
        <td data-title="'Failed'" sortable="'failed'">{{queryInfo.failed}}</td>
        <td data-title="''"><button class="btn">Detail</button></td>
    </tr>
  </table> 
<div>
Nethermost answered 15/4, 2015 at 17:34 Comment(1)
Can you please look at my ngtable filter problem here?Lezlie
S
4

It looks like you're experiencing an issue with ng-table itself, which is a 3rd party library that's not really well supported.

As evidence of this, check out their Github page, which has over 200 open issues and 1,200 stars (1:6):

https://github.com/esvit/ng-table

Compare this to a library like D3.js, which enjoys the full technical and financial support of the New York Times. It has 150 issues and over 30,000 stars (1:200).

https://github.com/mbostock/d3

Large number of open issues means people are finding bugs faster than the developers can solve them. More than likely, the developers have probably moved on to new projects and are no longer interested in maintaining this library.

In other words, you've run into a functionality limitation of the library itself. When this happens, you have two options,

1. Stop using the library and select a new library, or build your own.

Sadly I have looked into this and there is not a really good angular table library right now

2. Fork the repository, learn the inner workings of the library, implement the feature you want yourself, and make a pull request.

Just so this is not a total bummer, maybe UI-Grid can help you where ng-table has failed.

http://ui-grid.info/

Disclosure: I am not affiliated with ui-grid and am still pretty much on the fence over whether it is even good

Sideshow answered 13/2, 2015 at 20:23 Comment(2)
I'm one of the authors of ui-grid and I'm also on the fence :) It is slowly getting better, though. Just to play devil's advocate, it's hard to correlate # of open issues with quality. Angular itself has over 800 open issues currently. The imbalance is between community use and admin/development team size; there's just too many users for the team to keep up with.Ume
It is true that it doesn't correlate to quality, but a large number of open issues in a repo indicates that the library has some health issues (and Angular does, lots.)Sideshow
L
0

In our situation we have used ng-table-dynamic for us to have a set of custom columns those custom columns needs to be map on list of users from the database at code level. Since mapping is manually happening in the code level we find a unique way of handling filtering and sorting with ng-table-dynamic.

Here is the code


          function initialize() {
    this.$scope.userList = new this.NgTableParams({
          page: 1,
          count: 10
        }, {
          counts: [],
          total: this.usersList.length,
          getData: (params) => { // handles custom sorting or filtering
            var results= [];
            
            
            //add logic here if any
            results = this.$filter('filter')(results, this.$scope.searchKey);
           
            params.total(results.length);
            pages = Math.ceil(params.total()/ params.count());
            if(pages > 1){
              results = results.slice((params.page() - 1)* params.count(), params.page() * params.count());
            }
            return results;
          }
        });
    }

Note: We used an declared variable results, where it will contain the data to be rendered to the table.

Also in controller we need to handle the custom search

here is the code:


    this.$scope.$watch('searchKey',(newValue)=>{
          if(typeof newValue !== 'undefined'){
            this.$scope.userList.reload();// cause re-render of result in key-press
          }
    });

This code will trigger getData function of NgTableParams, allowing you to build your custom filter.

Note: You need to also preserve the result array so you need to store in in $scope variable

Code will look like this


    function foo() {
        Provider('Method', (error, users) => {
          if (!error) {
            this.usersList  = users;
            this.$scope.userList.reload(); // cause render of results to the table
            this.$scope.$apply();
          }
        });
      }

Ladder answered 7/4, 2017 at 4:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.