title: Server-side paging+filtering+sorting for ng-grid with WebAPI
body: <p>I'm trying to create a simple working example of using ng-grid with ASP.NET WebAPI. Thus, I started from the server-side paging example in the ng-grid examples page (<a href="http://angular-ui.github.io/ng-grid/">http://angular-ui.github.io/ng-grid/</a>); anyway, my grid always shows empty columns, even if when debugging I can confirm that data are received properly. Probably I'm just missing something in the grid setup, but all the samples I found look similar to mine. Could anyone help? Here is what I did:</p>
  
  <p><b>Update #1</b>: the suggested solution seems to work but only for the 1st page. Whenever I move to a new page or do any other operation requiring a refresh, the displayed data stay the same even if the server returned data change as expected. Also, from all the code samples I found it seems the correct way of setting data is just replacing the array member value rather than emptying and filling it again. I tried with apply as suggested in <a href="https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ">https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ</a>, but I get the same result.</p>
  
  <hr/>
  
  <h2>Server side</h2>
  
  <p>Just create a new MVC4 app, update NuGet packages and add angular and ng-grid packages. 
  My fake data model is represented by the Item class:</p>
  
  <pre><code>public sealed class Item
  {
      public int Id { get; set; }
      public string Name { get; set; }
      public int Age { get; set; }
      public bool IsFemale { get; set; }
  }
  </code></pre>
  
  <p>I also add a couple of models for dealing with paging, filtering and sorting various sets of data (I find easier to have a common paging base model -PagedFilter-, and a number of derived models):</p>
  
  <pre><code>public class PagedFilter
  {
      private int _nPageSize;
      private int _nPageNumber;
  
      public int PageSize
      {
          get { return _nPageSize; }
          set
          {
              if (value &lt; 1) throw new ArgumentOutOfRangeException("value");
              _nPageSize = value;
          }
      }
  
      public int PageNumber
      {
          get { return _nPageNumber; }
          set
          {
              if (value &lt; 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;
      }
  }
  </code></pre>
  
  <p>Here is the ItemFilter:</p>
  
  <pre><code>public class ItemFilter : PagedFilter
  {
      public List&lt;string&gt; SortFields { get; set; }
      public List&lt;string&gt; SortDirections { get; set; }
      public string Name { get; set; }
      public int? MinAge { get; set; }
      public int? MaxAge { get; set; }
  }
  </code></pre>
  
  <p>Then I add an API controller for getting items:</p>
  
  <pre><code>public class ItemController : ApiController
  {
      // fake data
      private readonly List&lt;Item&gt; _items;
  
      public ItemController()
      {
          Random rnd = new Random();
          _items = new List&lt;Item&gt;();
          char c = 'a';
  
          for (int i = 0; i &lt; 1000; i++)
          {
              _items.Add(new Item
                              {
                                  Id = i,
                                  Age = rnd.Next(1, 100),
                                  IsFemale = ((i &amp; 1) == 0),
                                  Name = String.Format(CultureInfo.InvariantCulture, "{0:00000}-{1}",
                                      i, new string(c, 5))
                              });
              if (++c &gt; 'z') c = 'a';
          }
      }
  
      public dynamic Get([FromUri] ItemFilter filter)
      {
          var items = _items.AsQueryable();
  
          // filtering
          if (!String.IsNullOrEmpty(filter.Name))
              items = items.Where(i =&gt; i.Name.Contains(filter.Name));
  
          if (filter.MinAge.HasValue)
              items = items.Where(i =&gt; i.Age &gt;= filter.MinAge.Value);
  
          if (filter.MaxAge.HasValue)
              items = items.Where(i =&gt; i.Age &lt;= 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()
                     };
      }
  }
  </code></pre>
  
  <h2>Client side</h2>
  
  <p>On the client side, my angular app is just a single controller modeled on the ng-grid sample: thus I directly add properties to $scope, even if in a real-world scenario I'd rather use a model (probably generated from a TypeScript class). HTML:</p>
  
  <pre><code>&lt;div ng-app="MyApp" ng-controller="MainController"&gt;
      &lt;div ng-grid="gridOptions" style="height: 400px"&gt;
      &lt;/div&gt;
  &lt;/div&gt;
  </code></pre>
  
  <p>JS:</p>
  
  <pre><code>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 &amp;&amp; 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();
  }]);
  </code></pre>
  
  <p>In my code, the success callback is called, and I can browse all the returned items in data.items. Yet, nothing is displayed in the grid. No error appears in the console.</p>