Reputation: 4559
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 (http://angular-ui.github.io/ng-grid/); 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:
Update #1: 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 https://groups.google.com/forum/#!searchin/angular/nggrid/angular/vUIfHWt4s_4/oU_C9w8j-uMJ, but I get the same result.
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:
public sealed class Item
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool IsFemale { get; set; }
}
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):
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;
}
}
Here is the 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; }
}
Then I add an API controller for getting items:
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()
};
}
}
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:
<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();
}]);
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.
Upvotes: 24
Views: 51560
Reputation: 3611
Last documentation is quite explicit about this question : http://ui-grid.info/docs/#/tutorial/308_external_filtering
My resulting code :
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);
});
OK now client side is done ! You have to process it server side, I can't help you with .Net WebAPI since I'm PHP/Mysql driving ...
Upvotes: 0
Reputation: 4212
It might help too
The HTML code-sample
<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>
The AngulaJs code-sample
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
};
});
Upvotes: 1
Reputation: 2381
Just do like in sample on Angular website:
$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));
});
Upvotes: -3
Reputation: 17
I have recently been working with ng-grid. I ran into similar issues where I was referencing the new version of AngularJS. Make sure you are referencing angular min file 1.0.2.
Here is my client side code for the ng-grid with pagination. It works perfectly once having implemented the proper version of 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
};
});
Upvotes: -1
Reputation: 4559
After experimenting a bit, I think I found the correct code. This post about $apply helped me a bit: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html. In fact, if I understand well the call to apply should not be needed at all, given that my data are coming from $http which already provides this. So, I ended with just setting the scope items variable in the success callback. Here is the full JS again, hope this can help some newcomer like me. Now I'm going to expand the test with TypeScript models, services and all the real-world stuff: I fear I'll have to make some new post... :)
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();
}]);
(As a sidenote, you can see from the code that I'm passing a single string for sort data, rather than two arrays for fields and directions. In fact, I could not find the right way of receiving arrays as members of my input model in the C# controller; so I'm just passing a single string where each field name is prefixed by + or - according to the ascending/descending direction).
Upvotes: 13
Reputation: 42669
You are setting datasource on ng-grid to items
but then you are never updating the items array on server success callback.
On succcess callback do something like this
$scope.totalServerItems = data.totalItems;
angular.forEach(data.items, function(item) {
$scope.items.push(item);
});
Upvotes: 4