【问题标题】:Server-side paging+filtering+sorting for ng-grid with WebAPI使用 WebAPI 对 ng-grid 进行服务器端分页+过滤+排序
【发布时间】: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中所有返回的项目。然而,网格中没有显示任何内容。控制台中没有出现错误。

【问题讨论】:

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


    【解决方案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));
                    });
            

            【讨论】:

              猜你喜欢
              • 2019-04-30
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-06-22
              • 2013-06-28
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多