关于javascript:如何在异步调用中填充ng-table上的选择过滤器

How to populate select filters on ng-table from async call

TL:DR

如何使用ajax/json填充包含"select"过滤器的ng表?

Pluk显示问题:http://plnkr.co/zn09lv

细节

我正在尝试处理angualrjs和ng表扩展,虽然我可以得到一些很好的带有工作过滤器的表,比如当我使用javascript中定义的静态数据时,一旦我试图将实际数据加载到表中,我就遇到了一个障碍。

ng表的主体是正确填充的,只要我只使用文本过滤器,一切都会正常工作:

1
2
3
        <td data-title="'Name'" filter="{ 'Name': 'text' }" sortable="'Name'">
            {{user.Name}}
        </td>

工作很好。

但是,如果我将此更新为使用选择筛选器:

1
2
3
        <td data-title="'Name'" filter="{ 'Name': 'select' }" sortable="'Name'"  filter-data="Names($column)">
            {{user.Name}}
        </td>

我遇到了一个同步问题,即在从服务器返回数据之前,总是先评估name变量。(可能在向服务器发送请求之前对varibale的名称进行了评估。)这意味着我得到了一个空的过滤器列表。

一旦数据从服务器返回-我似乎找不到更新select过滤器的方法。重新运行最初创建过滤器列表的代码似乎没有任何效果-我不确定如何触发ng表来重新检查其过滤器,以便不读取更新的变量。在异步调用完成之前,我也找不到一种方法来推迟变量的计算。

对于我的javascript,我几乎使用了ng table github页面中的示例Ajax代码,并在其中添加了select过滤器的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    $scope.tableParams = new ngTableParams({
        page: 1,            // show first page
        count: 10,          // count per page
        sorting: {
            name: 'asc'     // initial sorting
        }
    }, {
        total: 0,           // length of data
        getData: function($defer, params) {
            // ajax request to api
            Api.get(params.url(), function(data) {
                $timeout(function() {
                    // update table params
                    var orderedData = params.sorting ?
                    $filter('orderBy')(data.result, params.orderBy()) :
                    data.result;
                    orderedData = params.filter ?
                    $filter('filter')(orderedData, params.filter()) :
                    orderedData;

                    $scope.users = orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count());

                    params.total(orderedData.length); // set total for recalc pagination
                    $defer.resolve($scope.users);
                }, 500);
            });
        }
    });

    var inArray = Array.prototype.indexOf ?
    function (val, arr) {
        return arr.indexOf(val)
    } :
    function (val, arr) {
        var i = arr.length;
        while (i--) {
            if (arr[i] === val) return i;
        }
        return -1
    };
$scope.names = function(column) {
    var def = $q.defer(),
        arr = [],
        names = [];
    angular.forEach(data, function(item){
        if (inArray(item.name, arr) === -1) {
            arr.push(item.name);
            names.push({
                'id': item.name,
                'title': item.name
            });
        }
    });
    def.resolve(names);
    return def;
};

我尝试过添加一个额外的$q.defer()并包装初始数据,后面跟着$scope.names函数,但是我对promise和defer的理解还不够强,无法使任何东西正常工作。

在Github上有几条注释表明这是ng表中的一个bug,但我不确定是否是这样,或者我只是在做一些愚蠢的事情。

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

关于如何继续大受赞赏的建议

-凯恩-


我有一个相似但稍微复杂的问题。我希望能够动态地更新过滤器列表,这看起来完全可行,因为无论如何它们应该只在$scope变量中。基本上,我希望,如果我有$scope.filterOptions = [];,那么我可以设置filter-data="filterOptions",对该列表的任何更新都会自动反映出来。我错了。

但我找到了一个我认为很好的解决方案。首先,您需要覆盖ngtable select过滤器模板(如果您不知道如何做,它涉及使用$templateCache,您需要覆盖的键是'ng-table/filters/select.html'

在普通模板中,您会发现类似于此ng-options="data.id as data.title for data in $column.data",问题是$column.data是一个固定值,在更新$scope.filterOptions时不会改变。

我的解决方案是只传递$scope键作为筛选数据,而不是传递整个选项列表。因此,我将通过filter-data="'filterOptions'",而不是filter-data="filterOptions",然后在模板中进行一个小的更改,如:ng-options="data.id as data.title for data in {{$column.data}}"

显然,这是对Select过滤器工作方式的重大更改。在我的例子中,这是一个只有一张表的非常小的应用程序,但是您可能会担心这样的更改会破坏您的其他选择。如果是这样,您可能希望将此解决方案构建到自定义筛选器中,而不只是重写"select"。


您可以通过自定义过滤器实现这一点:

表格上的标准选择过滤器代码显示:

1
2
3
4
5
<select ng-options="data.id as data.title for data in column.data"
    ng-model="params.filter()[name]"
    ng-show="filter == 'select'"
    class="filter filter-select form-control" name="{{column.filterName}}">
</select>

当您调用此数据时,您会传递:filter-data="names($column)"和ngtable负责为您获取数据。我不知道为什么这不适用于外部资源。正如你所指出的,我敢打赌这与美元栏和承诺有关。

为了避免这种情况,我在代码中做了一个快速的变通。编写自己的选择筛选器模板,如:

1
2
3
4
<select id="filterTest" class="form-control"
    ng-model="tableParams.filter()['test']"
    ng-options="e.id as e.title for e in externaldata">
</select>

在控制器中获取此外部数据:

1
$scope.externaldata = Api.query(); // Your custom api call

它工作得很好,但是我的数据上确实有一个id,所以不需要name函数。

我知道这个解决方案不是最佳的。让我们看看是否有人在这里写的比这个"解决方法"更多,并启发我们。甚至埃斯维特有时也在这里;)


这对我很有用:

HTML:

1
2
3
<td data-title="'Doc type'" filter="{ 'doc_types': 'select' }" filter-data="docTypes()" sortable="'doc_types'">
    {{task.doc_type}}
</td>

AngularJS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$scope.docTypes = function ($scope)
{
    var def = $q.defer();
    //var docType = [
    //    {'id':'4', 'title':'Whatever 1'},
    //    {'id':'9', 'title':'Whatever 2'},
    //    {'id':'11', 'title':'Whatever 3'}
    //];

    // Or get data from API.
    // Format should be same as above.
    var docType = $http.get('http://whatever.dev', {
        params: { param1: data1 }
    });

    //Or with Restangular
    var docType = Restangular.all('/api/doctype').getList();

    def.resolve(docType);
    return def;
};


如@andi_n所述,您可以使用自定义过滤器来实现。

通过承诺(Angular中的$Q服务)很容易实现异步数据填充,这是安迪关于承诺的有趣文章。

您可以修改$scope.names方法并添加$http服务,该服务返回异步数据并将延迟对象解析为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$scope.names = function(column) {
  var def = $q.defer();

  /* http service is based on $q service */
  $http({
    url: siteurl +"app/application/fetchSomeList",
    method:"POST",

  }).success(function(data) {

    var arr = [],
      names = [];

    angular.forEach(data, function(item) {
      if (inArray(item.name, arr) === -1) {
        arr.push(item.name);
        names.push({
          'id': item.name,
          'title': item.name
        });
      }
    });
   
    /* whenever the data is available it resolves the object*/
    def.resolve(names);

  });

  return def;
};


我遇到了类似的问题,但不想进行额外的Ajax调用来获取过滤器值。

OP代码的问题在于,在填充$scope.data之前,过滤器数据函数会执行。为了解决这个问题,我使用角度$watch来监听$scope.data的变化。一旦$scope.data有效,就可以正确填充过滤器数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
        $scope.names2 = function () {
        var def = $q.defer(),
             arr = [],
                names = [];
        $scope.data ="";
        $scope.$watch('data', function () {


            angular.forEach($scope.data, function (item) {
                if (inArray(item.name, arr) === -1) {
                    arr.push(item.name);
                    names.push({
                        'id': item.name,
                        'title': item.name
                    });
                }
            });

        });
        def.resolve(names);
        return def;
    };

原始的Pluk随更改而分叉:http://plnkr.co/edit/sjxvppkr2ziyasyavbqa

也可以在$watch上看到这个问题:如何使用$scope.$watch和$scope.$apply在AngularJS中?


我用Diablo提到的$q.defer()解决了这个问题。

但是,IS代码实际上非常简单和简单:

在HTML中:

1
<td ... filter-data="countries">

在控制器中:

1
2
3
4
$scope.countries = $q.defer();
$http.get("/getCountries").then(function(resp){
  $scope.countries.resolve(resp.data.data);
})


我所做的只是将select标记与值放在一起,让ng模型返回过滤器的值。

这很有用,因为我需要翻译纯文本。

1
2
3
4
5
6
7
8
9
10
11
<td data-title="'Status'| translate" ng-bind ="("cc_assignment_active"== '0') ? ('Inactive' | translate) : ('Active'| translate)"
                    filter="{ cc_assignment_active: 'select3'}">

</td>

<script id="ng-table/filters/select3.html" type="text/ng-template">
<select  class="filter filter-select form-control"  ng-model="params.filter()[name]" name="{{name}}">
    <option active value="" translate>---All---</option>
    <option value="1" translate>Active</option>
    <option value="0" translate>Inactive</option>
</select>

"First, you need to override the ngTable select filter template (in case you don't know how to do this, it involves using $templateCache and the key you need to override is 'ng-table/filters/select.html')."

我在ng表的脚本下面添加了覆盖的脚本,一切都运行良好…

1
2
<script id="ng-table/filters/select.html" type="text/ng-template">
 <select ng-options="data.id as data.title for data in {{$column.data}}" ng-table-select-filter-ds="$column" ng-disabled="$filterRow.disabled" ng-model="params.filter()[name]" class="filter filter-select form-control" name="{{name}}"> <option style="display:none" value=""></option> </select>