服务器端分页+过滤+使用WebAPI对ng-grid进行排序

时间:2013-07-22 11:11:04

标签: angularjs asp.net-web-api ng-grid

我正在尝试创建一个使用ng-grid和ASP.NET WebAPI的简单工作示例。因此,我从ng-grid示例页面(http://angular-ui.github.io/ng-grid/)中的服务器端分页示例开始;无论如何,我的网格总是显示空列,即使在调试时我可以确认数据是否正确接收。可能我只是在网格设置中遗漏了一些东西,但我发现的所有样本看起来都与我的相似。有人可以帮忙吗?这是我做的:

更新#1 :建议的解决方案似乎有效但仅适用于第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()
                   };
    }
}

客户端

在客户端,我的角度应用程序只是在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中的所有返回项。然而,网格中没有显示任何内容。控制台中没有出现错误。

6 个答案:

答案 0 :(得分:13)

经过一番实验,我想我找到了正确的代码。这篇关于$ apply的帖子对我有所帮助:http://jimhoskins.com/2012/12/17/angularjs-and-apply.html。事实上,如果我理解的话,根本不应该要求申请,因为我的数据来自$ 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#控制器中;所以我只是传递一个字符串,其中每个字段名称都以+或 - 为前缀,根据上升/下降方向)。

答案 1 :(得分:4)

您正在将ng-grid上的数据源设置为items,但是您永远不会更新服务器成功回调上的items数组。

在成功回调上执行类似这样的操作

$scope.totalServerItems = data.totalItems;
angular.forEach(data.items, function(item) {
   $scope.items.push(item);
});

答案 2 :(得分:1)

它可能也有帮助

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

答案 3 :(得分:0)

我最近一直在使用ng-grid。我遇到了类似的问题,我引用了新版本的AngularJS。确保引用角度最小文件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
};

});

答案 4 :(得分:0)

上一篇文档对此问题非常明确: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驱动...

答案 5 :(得分:-3)

就像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));
        });