Our team is building a table widget in ServiceNow and decided to give ngTable a try. We've loaded the dependency and tested that it renders correctly with the example on the github site. We now want to load our data from an object array in the server script called data.onbCase. However, when we try to load the data this way, we get an error message in the console saying:
TypeError: Cannot read property 'length' of undefined
In our client script we have total: c.onbCase.length, and are unsure why this is creating an error. Are we missing something here?
<div class="panel panel-default" style="margin-bottom:200px;">
<div class="panel-heading">
<span class="panel-title"><i class="fa fa-{{c.glyph}}" aria-hidden="true"></i> {{c.title}}</span>
</div>
<div class="panel-body" ng-if="c.data.loading">
<span><i class="fa fa-spinner fa-spin fa-3x fafw"></i>
<span class="sr-only">Loading...</span>
</span>
</div>
<div class="panel-body table-responsive" ng-if="!c.data.loading">
<h4 ng-if="c.options.filter">Table Filter: {{c.options.filter}}</h4>
<table id="print_table" class="display table table-striped table-hover" ng-table ="usersTable" show-filter=true>
<tr ng-click="c.onWidget('batch_qa_form_list',item.sys_id, item.short_description);"
ng-repeat="item in c.onbCase track by $index"
ng-if="item.case_visible">
<td ng-show="c.options.case_number" data-title="{{item.number}}" sortable="'number'" filter="{ 'number': 'text' }">{{item.number}}</td>
<td ng-show="c.options.last_name" data-title="{{item.last_name}}" sortable="'last_name'" filter="{ 'last_name': 'text' }">{{item.last_name}}</td>
<td ng-show="c.options.first_name" data-title="{{item.first_name}}" sortable="'first_name'" filter="{ 'first_name': 'text' }">{{item.first_name}}</td>
<td ng-show="c.options.case_short_description" data-title="{{item.short_description}}" sortable="'short_description'" filter="{ 'short_description': 'text' }">{{item.short_description}}</td>
<td ng-show="c.options.start_date" data-title="{{item.start_date}}" sortable="'start_date'" filter="{ 'start_date': 'text' }">{{item.start_date | date:'shortDate':'Z'}}</td>
<td ng-show="c.options.work_location" data-title="{{item.location}}" sortable="'location'" filter="{ 'location': 'text' }">{{item.location}}</td>
<td ng-show="c.options.form_review_status" data-title="{{item.form_review_status}}" sortable="'all_approved'" filter="{ 'all_approved': 'text' }">
<i ng-if="item.all_approved=='All Forms Reviewed'" class="fa fa-check-circle fa-lg success"></i>
<i ng-if="item.all_approved=='Pending Review'" class="fa fa-exclamation-circle fa-lg warning" uib-tooltip="{{item.number_pending}} items pending review" tooltip-placement="left" title=""></i>
</td>
</tr>
</table>
</div>
</div>
function($scope, spUtil, spModal, $filter, ngTableParams) {
var c = this;
//Set value to show loading spinner
c.data.loading = true;
function initialize() {
c.server.get({
action: 'retrieve_data'
}).then(function(response) {
//Set value to hide loading spinner
c.data.loading = false;
//Get link data
c.onbCase = response.data.onbCase;
});
}
initialize();
$scope.usersTable = new ngTableParams({
page: 1,
count: 5
}, {
total: c.onbCase.length,
getData: function ($defer, params) {
$scope.data = params.sorting() ? $filter('orderBy')(c.onbCase, params.orderBy()) : c.onbCase;
$scope.data = params.filter() ? $filter('filter')($scope.data, params.filter()) : $scope.data;
$scope.data = $scope.data.slice((params.page() - 1) * params.count(), params.page() * params.count());
$defer.resolve($scope.data);
}
});
spUtil.recordWatch($scope, "x_dnf_federal_hr_o_federal_form_review", "", function(name, data) {
initialize();
});
}
This is a classic example of not understanding the order or operations of JS promises. Your ngTable
initialization is getting called BEFORE THE DATA HAS RETURNED.
The problem is that your call to ngTableParams()
comes directly after making your server.get
call. You don't want to run this until control moves to within the .then()
response. You do this by wrapping the new ngTableParams()
call in a function and calling it when the server call has completed. This is the correct way to structure your code:
function initialize() {
c.server.get({
action: 'retrieve_data'
}).then(function(response) {
//Set value to hide loading spinner
c.data.loading = false;
//Get link data
c.onbCase = response.data.onbCase;
// don't run the ngTable init until the http call has returned
ngTableInit();
});
}
initialize();
//wrap your ngTableParams constructor in a function
function ngTableInit() {
$scope.usersTable = new ngTableParams({
page: 1,
count: 5
}, {
total: c.onbCase.length,
getData: function ($defer, params) {
...
}
});
}
If you set breakpoints in your browser's debugger in your original code, you will see that $scope.usersTable = new ngTableParams({...})
is called well before the server call ever returns and c.onbCase
is undefined.