Как заполнить фильтры выбора на 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>