Как заполнить фильтры выбора на ng-таблице из асинхронного вызова

ТЛ: дг

Как я могу заполнить ng-таблицу, включая фильтры "select", используя ajax/json?

Plunk, показывающий проблему: http://plnkr.co/Zn09LV


Деталь

Я пытаюсь справиться с AngualrJS и расширением ng-table, и хотя я могу получить несколько хороших таблиц с рабочими фильтрами и, например, когда я использую статические данные, определенные в javascript, - как только я получу попытку загрузить реальные данные в таблицу Я попал в ловушку.

Основная часть ng-таблицы заполняется правильно и до тех пор, пока я использую только текстовый фильтр, кажется, что все работает:

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

Хорошо работает.

Однако, если я обновляю это, чтобы использовать фильтр select:

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

Я столкнулся с проблемой синхронизации, поскольку переменная имен всегда оценивается до того, как данные вернулись с сервера. (Возможно, varibale имен оценивается до отправки запроса на сервер.) Это означает, что я получаю пустой список для фильтра.

Как только данные вернутся с сервера - я не могу найти способ обновления фильтра выбора. Повторное выполнение кода, создающего список фильтров, изначально кажется неэффективным - я не уверен, как вызвать ng-таблицу для повторной проверки своих фильтров, чтобы обновленная переменная не читалась. Я также не могу определить способ отложить оценку переменной до завершения асинхронного вызова.

Для моего javascript я довольно часто использовал пример ajax-кода на странице ng-table GitHub и добавил к нему пример кода для фильтра select.

    $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, но мое понимание обещания и отсрочки недостаточно для того, чтобы получить все работает.

Есть несколько заметок о GitHub, которые указывают на то, что это ошибка в ng-таблице, но я не уверен, что этот случай или я просто делаю что-то глупое.

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

Указатели того, как правильно обрабатывать,

-Kaine -

Ответ 1

У меня была аналогичная, но немного более сложная проблема. Я хотел бы иметь возможность обновлять список фильтров динамически, что казалось вполне выполнимым, так как в любом случае они должны быть как раз в переменной $scope. В принципе, я ожидал, что, если у меня есть $scope.filterOptions = [];, тогда я могу установить filter-data="filterOptions", и любое обновление этого списка будет автоматически отражено. Я был неправ.

Но я нашел решение, которое, я думаю, довольно хорошо. Во-первых, вам необходимо переопределить шаблон фильтра выбора ngTable (если вы не знаете, как это сделать, он включает в себя использование $templateCache, а ключ, который вам нужно переопределить, - 'ng-table/filters/select.html').

В обычном шаблоне вы найдете что-то вроде этого ng-options="data.id as data.title for data in $column.data", и проблема в том, что $column.data является фиксированным значением, которое не изменится при обновлении $scope.filterOptions.

Мое решение состоит в том, чтобы передать только ключ в качестве данных фильтра вместо передачи всего списка параметров. Итак, вместо filter-data="filterOptions", я передам filter-data="'filterOptions'", а затем внесите небольшое изменение в шаблон, например: ng-options="data.id as data.title for data in {{$column.data}}".

Очевидно, это значительное изменение в том, как работает фильтр выбора. В моем случае это было для очень маленького приложения, у которого была только одна таблица, но вы можете быть обеспокоены тем, что подобное изменение нарушит ваши другие варианты. В этом случае вы можете захотеть создать это решение в пользовательский фильтр, а не просто переопределять "select".

Ответ 2

Вы можете достичь этого с помощью настраиваемого фильтра:

code для стандартного фильтра выбора на ngtable говорит:

<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 заботится о получении данных для вас. Я не знаю, почему это не работает с внешним ресурсом. Уверен, что это имеет какое-то отношение к столбцу $и обещанию, как вы указали.

Я сделал быстрый обходной путь в своем коде, чтобы этого избежать. Напиши свой собственный шаблон фильтра выбора, например:

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

Вы извлекаете эти внешние данные в свой контроллер:

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

Он отлично работает, но у меня есть id по моим данным, поэтому нет необходимости в функции name.

Я понимаю, что это решение не является оптимальным. Давайте посмотрим, если кто-то пишет здесь больше, чем этот "обходной путь" и просвещает нас. Даже esvit здесь иногда;)

Ответ 3

Это работает для меня:

HTML:

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

AngularJS:

$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;
};

Ответ 4

Как уже упоминалось @Andión, вы можете достичь настраиваемого фильтра.

Легко достичь асинхронной совокупности данных с Promises (услуга $q в Angular), интересная Энди Статья о Promises

Вы можете изменить метод $scope.names и добавить службу $http, которая возвращает асинхронные данные и разрешить отложенный объект как:

$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;
};

Ответ 5

Я столкнулся с подобной проблемой, но не хотел, чтобы дополнительный вызов AJAX получил значения фильтра.

Проблема с кодом OP заключается в том, что функция фильтрации данных выполняется до заполнения $scope.data. Чтобы обойти это, я использовал часы Angular $, чтобы прослушивать изменения в $scope.data. Как только значение $scope.data является допустимым, данные фильтра заполняются правильно.

        $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;
    };

Оригинальная панель, раздвоенная с изменением: http://plnkr.co/edit/SJXvpPQR2ZiYaSYavbQA

Также см. этот SO-запрос в $watch: Как использовать $scope. $watch и $scope. $apply в AngularJS?

Ответ 6

Я решил проблему с $q.defer(), как упоминалось Diablo

Однако код действительно довольно простой и понятный:

в HTML:

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

в контроллере:

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

Ответ 7

"Во-первых, вам нужно переопределить шаблон фильтра выбора ngTable (если вы не знаете, как это сделать, он включает использование $templateCache, а ключ, который вам нужно переопределить, - это" ng-table/filters/select ". HTML" )".

Я добавил переопределенный script ниже script ng-таблицы, и все сработало хорошо...

<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>
</script>

Ответ 8

Что я сделал, просто поместил тег select со значениями и попросил ng-model вернуть значения для фильтра.

Это было полезно, так как мне нужно было перевести простой текст.

<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>