使用 WebAPI 对 ng-grid 进行服务器端分页+过滤+排序
Posted
技术标签:
【中文标题】使用 WebAPI 对 ng-grid 进行服务器端分页+过滤+排序【英文标题】:Server-side paging+filtering+sorting for ng-grid with WebAPI 【发布时间】:2013-07-21 02:24:32 【问题描述】:我正在尝试创建一个简单的工作示例,将 ng-grid 与 ASP.NET WebAPI 结合使用。因此,我从 ng-grid 示例页面 (http://angular-ui.github.io/ng-grid/) 中的服务器端分页示例开始;无论如何,我的网格总是显示空列,即使在调试时我可以确认数据已正确接收。可能我只是在网格设置中遗漏了一些东西,但我发现的所有样本看起来都与我的相似。有人可以帮忙吗?这是我所做的:
更新 #1:建议的解决方案似乎有效,但仅适用于第一页。每当我移动到新页面或执行任何其他需要刷新的操作时,即使服务器按预期返回数据更改,显示的数据也会保持不变。此外,从我发现的所有代码示例中,似乎设置数据的正确方法只是替换数组成员值,而不是清空并再次填充它。我按照https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ 中的建议尝试了应用,但得到了相同的结果。
服务器端
只需创建一个新的 MVC4 应用程序,更新 NuGet 包并添加 angular 和 ng-grid 包。 我的假数据模型由 Item 类表示:
public sealed class Item
public int Id get; set;
public string Name get; set;
public int Age get; set;
public bool IsFemale get; set;
我还添加了几个模型来处理分页、过滤和排序各种数据集(我发现更容易拥有一个通用的分页基础模型 -PagedFilter- 和一些派生模型):
public class PagedFilter
private int _nPageSize;
private int _nPageNumber;
public int PageSize
get return _nPageSize;
set
if (value < 1) throw new ArgumentOutOfRangeException("value");
_nPageSize = value;
public int PageNumber
get return _nPageNumber;
set
if (value < 1) throw new ArgumentOutOfRangeException("value");
_nPageNumber = value;
public int TotalItems get; set;
public int TotalPages
get return (int)Math.Ceiling((double)(TotalItems / PageSize));
public PagedFilter()
_nPageSize = 20;
_nPageNumber = 1;
这里是 ItemFilter:
public class ItemFilter : PagedFilter
public List<string> SortFields get; set;
public List<string> SortDirections get; set;
public string Name get; set;
public int? MinAge get; set;
public int? MaxAge get; set;
然后我添加一个用于获取项目的 API 控制器:
public class ItemController : ApiController
// fake data
private readonly List<Item> _items;
public ItemController()
Random rnd = new Random();
_items = new List<Item>();
char c = 'a';
for (int i = 0; i < 1000; i++)
_items.Add(new Item
Id = i,
Age = rnd.Next(1, 100),
IsFemale = ((i & 1) == 0),
Name = String.Format(CultureInfo.InvariantCulture, "0:00000-1",
i, new string(c, 5))
);
if (++c > 'z') c = 'a';
public dynamic Get([FromUri] ItemFilter filter)
var items = _items.AsQueryable();
// filtering
if (!String.IsNullOrEmpty(filter.Name))
items = items.Where(i => i.Name.Contains(filter.Name));
if (filter.MinAge.HasValue)
items = items.Where(i => i.Age >= filter.MinAge.Value);
if (filter.MaxAge.HasValue)
items = items.Where(i => i.Age <= filter.MaxAge.Value);
// ...sorting (using Dynamic Linq) omitted for brevity...
// paging
int nTotalItems = items.Count();
items = items.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize);
return new
totalItems = nTotalItems,
items = items.ToArray()
;
客户端
在客户端,我的 Angular 应用程序只是一个以 ng-grid 示例为模型的控制器:因此我直接将属性添加到 $scope,即使在实际场景中我宁愿使用模型(可能从 TypeScript 类生成)。 html:
<div ng-app="MyApp" ng-controller="MainController">
<div ng-grid="gridOptions" style="height: 400px">
</div>
</div>
JS:
var app = angular.module('MyApp', ['ngGrid']);
app.controller('MainController', ['$scope', '$http', function ($scope, $http, $apply)
$scope.items = [];
// filter
$scope.filterOptions =
filterText: "",
useExternalFilter: true
;
// paging
$scope.totalServerItems = 0;
$scope.pagingOptions =
pageSizes: [25, 50, 100],
pageSize: 25,
currentPage: 1
;
// sort
$scope.sortOptions =
fields: ["name"],
directions: ["ASC"]
;
// grid
$scope.gridOptions =
data: "items",
columnDefs: [
field: "name", displayName: "Name", pinnable: true ,
field: "age", displayName: "Age", width: "60" ,
field: "isFemale", displayName: "F", width: "40"
],
enablePaging: true,
enablePinning: true,
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions,
keepLastSelected: true,
multiSelect: false,
showColumnMenu: true,
showFilter: true,
showGroupPanel: true,
showFooter: true,
sortInfo: $scope.sortOptions,
totalServerItems: "totalServerItems",
useExternalSorting: true,
i18n: "en"
;
$scope.refresh = function()
setTimeout(function ()
var p =
name: $scope.filterOptions.filterText,
pageNumber: $scope.pagingOptions.currentPage,
pageSize: $scope.pagingOptions.pageSize,
sortFields: $scope.sortOptions.fields,
sortDirections: $scope.sortOptions.directions
;
$http(
url: "/api/item",
method: "GET",
params: p
).success(function(data, status, headers, config)
$scope.totalServerItems = data.totalItems;
// SUGGESTION #1 -- empty and fill the array
/* $scope.items.length = 0;
angular.forEach(data.items, function (item)
$scope.items.push(item);
);
*/
// https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ
$scope.$apply(function () $scope.items = data.items; );
if (!$scope.$$phase)
$scope.$apply();
).error(function(data, status, headers, config)
alert(JSON.stringify(data));
);
, 100);
;
// watches
$scope.$watch('pagingOptions', function (newVal, oldVal)
if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage)
$scope.refresh();
, true);
$scope.$watch('filterOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.refresh();
, true);
$scope.$watch('sortOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.refresh();
, true);
$scope.refresh();
]);
在我的代码中,调用了成功回调,我可以浏览data.items中所有返回的项目。然而,网格中没有显示任何内容。控制台中没有出现错误。
【问题讨论】:
【参考方案1】:经过一番试验,我想我找到了正确的代码。这篇关于 $apply 的帖子对我有所帮助:http://jimhoskins.com/2012/12/17/angularjs-and-apply.html。事实上,如果我理解得很好,那么根本不需要调用 apply ,因为我的数据来自 $http 已经提供了这个。所以,我只在成功回调中设置了范围项变量。这是完整的JS,希望这可以帮助像我这样的新手。现在我将使用 TypeScript 模型、服务和所有现实世界的东西来扩展测试:我担心我将不得不发布一些新帖子...... :)
var app = angular.module('MyApp', ['ngGrid']);
app.controller('MainController', ['$scope', '$http', function ($scope, $http, $apply)
$scope.items = [];
// filter
$scope.filterOptions =
filterText: "",
useExternalFilter: true
;
// paging
$scope.totalServerItems = 0;
$scope.pagingOptions =
pageSizes: [25, 50, 100],
pageSize: 25,
currentPage: 1
;
// sort
$scope.sortOptions =
fields: ["name"],
directions: ["ASC"]
;
// grid
$scope.gridOptions =
data: "items",
columnDefs: [
field: "id", displayName: "ID", width: "60" ,
field: "name", displayName: "Name", pinnable: true ,
field: "age", displayName: "Age", width: "60" ,
field: "isFemale", displayName: "F", width: "40"
],
enablePaging: true,
enablePinning: true,
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions,
keepLastSelected: true,
multiSelect: false,
showColumnMenu: true,
showFilter: true,
showGroupPanel: true,
showFooter: true,
sortInfo: $scope.sortOptions,
totalServerItems: "totalServerItems",
useExternalSorting: true,
i18n: "en"
;
$scope.refresh = function()
setTimeout(function ()
var sb = [];
for (var i = 0; i < $scope.sortOptions.fields.length; i++)
sb.push($scope.sortOptions.directions[i] === "DESC" ? "-" : "+");
sb.push($scope.sortOptions.fields[i]);
var p =
name: $scope.filterOptions.filterText,
pageNumber: $scope.pagingOptions.currentPage,
pageSize: $scope.pagingOptions.pageSize,
sortInfo: sb.join("")
;
$http(
url: "/api/item",
method: "GET",
params: p
).success(function(data, status, headers, config)
$scope.totalServerItems = data.totalItems;
$scope.items = data.items;
).error(function(data, status, headers, config)
alert(JSON.stringify(data));
);
, 100);
;
// watches
$scope.$watch('pagingOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.refresh();
, true);
$scope.$watch('filterOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.refresh();
, true);
$scope.$watch('sortOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.refresh();
, true);
$scope.refresh();
]);
(作为旁注,您可以从代码中看到我传递一个字符串来排序数据,而不是两个数组来传递字段和方向。事实上,我找不到接收数组作为成员的正确方法我在 C# 控制器中的输入模型;所以我只是传递一个字符串,其中每个字段名称都以 + 或 - 为前缀,根据升序/降序方向)。
【讨论】:
【参考方案2】:您将 ng-grid 上的数据源设置为 items
,但是您永远不会在服务器成功回调时更新 items 数组。
在成功回调时做这样的事情
$scope.totalServerItems = data.totalItems;
angular.forEach(data.items, function(item)
$scope.items.push(item);
);
【讨论】:
谢谢,我没有意识到我需要填充 $scope.items!另一个问题:知道为什么我的控制器的 GET 方法现在被调用了两次吗? 客户端还是服务器端? 服务器端,WebApi 控制器的 Get 方法。此外,我的代码中肯定还有一些问题:当我更改页面时,服务器获取正确的参数并返回接下来的 N 项;如果我在 JS 成功处理程序中设置断点,我可以浏览这些新项目;但最后视图没有刷新,我一直看到第一页而不是第二页。【参考方案3】:或许也有帮助
HTML 代码示例
<html ng-app="myApp">
<head lang="en">
<meta charset="utf-8">
<title>Getting Started With ngGrid code-sample</title>
<script type="text/javascript" src="angular.js"></script>
<script type="text/javascript" src="ng-grid-1.3.2.js"></script>
</head>
<body ng-controller="MyCtrl">
<div class="gridStyle" ng-grid="gridOptions"></div>
</body>
</html>
AngulaJs 代码示例
var app = angular.module('myApp', ['ngGrid']);
app.controller('MyCtrl', function($scope, $http)
$scope.filterOptions =
filterText: "",
useExternalFilter: true
;
$scope.totalServerItems = 0;
$scope.pagingOptions =
pageSizes: [250, 500, 1000],
pageSize: 250,
currentPage: 1
;
$scope.setPagingData = function(data, page, pageSize)
var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
$scope.myData = pagedData;
$scope.totalServerItems = data.length;
if (!$scope.$$phase)
$scope.$apply();
;
$scope.getPagedDataAsync = function (pageSize, page, searchText)
setTimeout(function ()
var data;
if (searchText)
var ft = searchText.toLowerCase();
$http.get('jsonFiles/largeLoad.json').success(function (largeLoad)
data = largeLoad.filter(function(item)
return JSON.stringify(item).toLowerCase().indexOf(ft) != -1;
);
$scope.setPagingData(data,page,pageSize);
);
else
$http.get('jsonFiles/largeLoad.json').success(function (largeLoad)
$scope.setPagingData(largeLoad,page,pageSize);
);
, 100);
;
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);
$scope.$watch('pagingOptions', function (newVal, oldVal)
if (newVal !== oldVal && newVal.currentPage !== oldVal.currentPage)
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
, true);
$scope.$watch('filterOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
, true);
$scope.gridOptions =
data: 'myData',
enablePaging: true,
showFooter: true,
totalServerItems: 'totalServerItems',
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions
;
);
【讨论】:
【参考方案4】:我最近一直在使用 ng-grid。我在引用新版本的 AngularJS 时遇到了类似的问题。确保您引用 angular min 文件 1.0.2。
这是我的带有分页的 ng-grid 的客户端代码。一旦实现了正确版本的 Angular JS,它就可以完美运行。
var app = angular.module('myApp', ['ngGrid']);
app.controller('MyCtrl', function ($scope, $http)
// We needed to bring back mer becase we were using a variable that was being reassigned later on
var mer = [ Item: "Bottle", Pcode: 50, OHQ: 333, AQ: 33, Details: "CLICK" ,
Item: "Bottle", Pcode: 43, OHQ: 2350, AQ: 1250, Details: "CLICK" ,
Item: "Bottle", Pcode: 27, OHQ: 4000, AQ: 3000, Details: "CLICK" ,
Item: "Bottle", Pcode: 29, OHQ: 55, AQ: 10, Details: "CLICK" ,
Item: "Bottle", Pcode: 34, OHQ: 27, AQ: 2, Details: "CLICK" ,
Item: "Bottle", Pcode: 50, OHQ: 111, AQ: 33, Details: "CLICK" ,
Item: "Bottle", Pcode: 43, OHQ: 123, AQ: 1250, Details: "CLICK" ,
Item: "Bottle", Pcode: 27, OHQ: 1234, AQ: 3000, Details: "CLICK" ,
Item: "Bottle", Pcode: 29, OHQ: 5678, AQ: 10, Details: "CLICK" ,
Item: "Bottle", Pcode: 34, OHQ: 0, AQ: 2, Details: "CLICK" ];
$scope.filterOptions =
filterText: "",
useExternalFilter: false
;
$scope.totalServerItems = 0;
$scope.pagingOptions =
pageSizes: [5, 10],
pageSize: 5,
currentPage: 1
;
$scope.setPagingData = function (data, page, pageSize)
var pagedData = data.slice((page - 1) * pageSize, page * pageSize);
$scope.myData = pagedData;
$scope.totalServerItems = data.length;
if (!$scope.$$phase)
$scope.$apply();
;
// I rearranged some of the code in this function. I noticed we were calling the same function
// in the end just with a slightly different set of data....so instead of having 18-ish lines of code
// we have 12 (YAY)
$scope.getPagedDataAsync = function (pageSize, page, searchText)
setTimeout(function ()
var data = mer;
if (searchText)
var ft = searchText.toLowerCase();
data = mer.filter(function (item)
JSON.stringify(item).toLowerCase().indexOf(ft) != -1;
);
$scope.setPagingData(data, page, pageSize);
, 100);
;
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage);
$scope.$watch('pagingOptions', function (newVal, oldVal)
// Got rid of the other check here...this is what was causing the filter to not change the data when it changed.
if (newVal !== oldVal)
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
, true);
$scope.$watch('filterOptions', function (newVal, oldVal)
if (newVal !== oldVal)
$scope.getPagedDataAsync($scope.pagingOptions.pageSize, $scope.pagingOptions.currentPage, $scope.filterOptions.filterText);
, true);
$scope.gridOptions =
data: 'myData',
enablePaging: true,
showFooter: true,
totalServerItems: 'totalServerItems',
pagingOptions: $scope.pagingOptions,
filterOptions: $scope.filterOptions
;
);
【讨论】:
【参考方案5】:最后一个文档对这个问题非常明确:http://ui-grid.info/docs/#/tutorial/308_external_filtering
我的结果代码:
var pagination =
pageNumber: 1,
pageSize: 10,
// list fields to be sorted
sort: [field:'dup_percentage', direction:'desc'],
// list fields to be filtered
filter: []
;
$scope.gridOptions =
enableFiltering: true,
useExternalFiltering: true,
columnDefs: [...],
onRegisterApi: function( gridApi )
$scope.gridApi = gridApi;
$scope.gridApi.core.on.filterChanged( $scope, function()
var grid = this.grid;
// reset filters
pagination.filter = [];
// loop over all columns
angular.forEach(grid.columns, function(column, i)
// loop over filters
if(typeof column.filters!==undefined)
angular.forEach(column.filters, function(filter, j)
// add column name and value to filter array
// to be send server side
if(typeof filter.term!=undefined && filter.term!==undefined)
//console.log('add filter', column:column.name, search:filter.term);
pagination.filter.push(column:column.name, search:filter.term);
);
);
// when user types it's search term
// server would be hitting too much
// so we add 500ms throttle
if (angular.isDefined($scope.filterTimeout))
$timeout.cancel($scope.filterTimeout);
$scope.filterTimeout = $timeout(function ()
// use pagination var which contains all info
// needed server side
getPage();
, 500);
);
好的,现在客户端完成了!你必须在服务器端处理它,我无法帮助你使用 .Net WebAPI,因为我是 php/mysql 驱动...
【讨论】:
【参考方案6】:就像 Angular 网站上的示例一样:
$http(
url: "/payments/GetPayments",
method: "GET",
params: p
).success(function(data, status, headers, config)
// Как в примере
$scope.items = data.items;
$scope.totalServerItems = data.totalItems;
if (!$scope.$$phase)
$scope.$apply();
).error(function(data, status, headers, config)
alert(JSON.stringify(data));
);
【讨论】:
以上是关于使用 WebAPI 对 ng-grid 进行服务器端分页+过滤+排序的主要内容,如果未能解决你的问题,请参考以下文章
角度 highchart 和 ng-grid 标题不考虑帧宽度