I already used ngTable with static data before, it worked well (listing, sorting and filtering). I'm using ngTable v 0.8.3
My Goal
This time I want to create a ngTable with data loaded from a Web Service.
I made a custom factory to be able to create my ngTable with future others entities.
Issue
It doesn't work and I have no JS console errors. When I debug, I can clearly see that my method query from my service Employee return me my complete and correct objet containing employees details. I enter in the getData() function, the ngTable is being created, but with empty data.
Relevant and lightweighten code
Here is my HTML markup :
<table ng-table="tableParams" class="table table-striped" show-filter="true">
<tbody>
<tr ng-repeat="employee in $data">
<td data-title="'employee.firstname' | translate" filter="{'firstname' : 'text'}" sortable="'firstname'"><a ui-sref="employee.detail({id:employee.id})">{{employee.firstname}}</a></td>
<td data-title="'employee.lastname' | translate" filter="{lastname : 'text'}" sortable="'lastname'">{{employee.lastname}}</td>
<td data-title="'employee.mail' | translate" filter="{mail : 'text'}" sortable="'mail'">{{employee.mail}}</td>
<td data-title="'employee.phone-number' | translate" filter="{phone_number : 'text'}" sortable="'phone_number'">{{employee.phone_number}}</td>
<td data-title="'employee.birthdate' | translate" filter="{birthdate : 'text'}" sortable="'birthdate'">{{employee.birthdate}}</td>
</tr>
</tbody></table>
my Angular controller :
myapp.controller('EmployeeController', function ($rootScope, $scope, Employee, ngTableParams, ngTableFactory) {
$scope.tableParams = ngTableFactory.create(20, {lastname: 'asc'}); // count, sorting
});
And my Angular factory :
myapp.factory('ngTableFactory', function(ngTableParams, Employee) {
return {
create: function(count, sorting) {
return new ngTableParams({
page: 1, // initial page
count: count, // count per page
sorting: sorting // initial sorting
}, {
total: 0,
getData: function($defer, params) {
Employee.query({page: params.page(), size: params.count()}, function(result) {
$defer.resolve(result);
});
}
});
}
}
});
Edit 1, progresses :
My items are being listed if I remove params from my query method, so it was a bad handling of my Employee service. But, the sort and filter does not work on my table by default. to make sorting works, I have to add this line on my ngTable factory, in the getData() function :
result = $filter('orderBy')(result, params.orderBy());
Normally, the sorting of basic elements works as I added the 'sortable' keyword in my HTML ngtable template columns, and as I'm fetching my data over $data item. Still investigating.
Thanks for your help mates !
As far as I can see, your code works.
Please see https://github.com/masa67/NgTable for a working example (based on Node). I did not change anything (except filled-in the missing parts, maybe the problem is there).
The table formatting is not correct, but at least the data is coming in.
Related
I have the following custom directive:
angular.module('Interfaces').directive('Interfaces', function() {
return {
restrict : 'A',
scope : {
minor : '#'
},
templateUrl : 'interfaces/interfaces.template.html',
controller : [ '$scope', 'InterfacesService', function InterfaceController($scope, InterfacesService) {
$scope.interfacesService = InterfacesService;
$scope.label = 'Interfaces';
$scope.optional = ($scope.minor == "true");
if ($scope.optional) {
$scope.label = '';
}
$scope.getInterfaces = function getInterfaces() {
return $scope.interfacesService.getInterfaces($scope.minor);
};
} ]
};
});
And the following template
<tr ng-class="{'optional': optional}"><td colspan="5">Just testing</td></tr>
<tr ng-class="{'optional': optional}" ng-repeat="interface in interfaces = (getInterfaces())" >
<td rowspan='{{interfaces.length}}' class='label-column' ng-if="$index === 0">{{label}}</td>
<td colspan='2' class='data'>{{interface.key}}</td>
<td colspan='2' class='data'>{{interface.value}}</td>
</tr>
I am using this directive as part of a table:
<table>
<tbody>
<!-- other table content -->
</tbody>
<tbody interfaces minor="true"></tbody>
<tbody interfaces minor="false"></tbody>
<tbody>
<!-- other table content -->
</tbody>
</table>
The first table row is just added for testing purposes. It correctly has the class "optional" according to the value of the variable 'optional'.
However, those table rows created by ng-repeat never have the "optional" class set, no mattter what the value of the 'optional' variable.
I have found the following article
Angular js Custom Directive on element with ng-repeat - Can't set CSS classes
which suggests using a priority of -1001 in my directive, but neither the 1001 of the original code in that post nor the -1001 suggested in the answer make a difference in my case.
Why is ng-class not applied on the element with the ng-repeat?
Ok, after talking to a friend it seems the problem was the following line in the template:
<tr ng-class="{'optional': optional}" ng-repeat="interface in interfaces = (getInterfaces())" >
Having put the getInterfaces() call in brackets seems to have lead to an endless iteration which stopped the ng-class from being applied. I had seen those error messages but did not make the connection between them and my issue.
I have solved the issue as follows:
Instead of retrieving the data from the InterfacesService, I am now passing them from the outer template as a parameter:
<tbody interfaces minor="true" interfaces="{{information.host.interfaces}}"></tbody>
<tbody interfaces minor="false" interfaces="{{information.host.interfaces}}"></tbody>
And in the directive I've added a new binding:
scope : {
minor : '#',
interfaces: '<'
},
And my interface template now directly references this new binding:
<tr ng-class="{'optional': optional}" ng-repeat="interface in interfaces" >
The reason for attempting to get this information from the InterfacesService instead of passing it into the directive was that I had tried that and failed, using the service had been a workaround.
I stumbled across the same problem again in a slightly different setting (see AngularJs: How to pass part of my data from one component to another) and the answer there (to use a '<' binding instead of '#' allowed me to get rid of the service and hence the
(getInterfaces())
part of my ng-repeat statement.
I have developed a ngtable with a filter. The pagination on the table does not work anymore though? When I select the show 10 rows on the bottom of the table it still shows 14 rows? How can i fix the pagination/indexing?
This is my table definition:
<table ng-table="tableParams" class="table">
<tr ng-repeat="account in $parent.filtered =(data | filter:search.accountName | filter:search.id)">
<td data-title="'id'">
{{account.account.accountId.id}}
</td>
<td data-title="'name'">
{{account.account.accountName}}
</td>
</tr>
</table>
plunkr:http://plnkr.co/edit/Rqt6px?p=preview
You need to figure pagination function by yourself. You may see ng-table's example in here.
var Api = $resource("/data");
this.tableParams = new NgTableParams({}, {
getData: function(params) {
// ajax request to api
return Api.get(params.url()).$promise.then(function(data) {
params.total(data.inlineCount); // recal. page nav controls
return data.results;
});
}
});
It first load all the data into Api. The params.url() contains current page and page count. It then use these two variable to return part of dataset. So you may need to figure out how to return this part of data.
Each click on my google Map adds new data to my array.
I have this array:
tableDATA = [{range:[],distances:[]}];
This are the push commands in the click function:
"total" and "round" are calculated integers
tableDATA[0].distances.push(total+' km');
tableDATA[0].range.push(round+' km');
This is my ng-table params code in javascript
$scope.tableParams = new ngTableParams(
{
page: 1, // show first page
count: 15, // count per page
sorting: {
distances: 'asc' // initial sorting
}
},
{
total: tableDATA[0].distances.length, // length of data
getData: function($defer, params) {
// use build-in angular filter
var orderedData = params.sorting() ?
$filter('orderBy')(tableDATA, params.orderBy()) : tableDATA;
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
}
);
And this is my ng-table code in html
<button ng-click="tableParams.sorting({})" class="btn btn-default pull-right">Clear sorting</button>
<p><strong>Sorting:</strong> {{tableParams.sorting()|json}}</p>
<table ng-table="tableParams" class="table">
<tr ng-repeat="point in $data">
<td data-title="'real'" sortable="'real'">{{point.distances}}</td>
<td data-title="'Kombi'" sortable="'Kombi'">{{point.range}}</td>
</tr>
</table>
The entries in the array won't be shown in multiple lines (one entry per line) but in one line with all array entries:
real Kombi
line 1: ["50.32 km","50.89 km", etc.] ["44.32 km","48.65 km", etc.]
line 2: won't be created
line 3: won't be created
What's wrong? Pls help
try using an angular foreach see https://docs.angularjs.org/api/ng/function/angular.forEach
I don't come to the right solution with forEach because I have an array behind the keys and no static values.. ?!
var tableDATA_arr = [];
tableDATA = {range:[],distances:[]};
tableDATA.distances.push(total+' km');
tableDATA.range.push(round+' km');
angular.forEach(tableDATA, function(value, key) {
this.push(key + ': ' + value);
}, tableDATA_arr);
<tr ng-repeat="point in $data">
<td><tr ng-repeat="distance in point.distances">
<td data-title="'real'" sortable="'real'">{{distance}}</td>
</tr></td>
then do the same for the other column
This is my code now. The data is retrievable(!), but the data still will be written in one line -.- i hate it :D
<tr ng-repeat="point in $data">
<td ng-repeat="dist in point.distances track by $index" data-title="'real'" sortable="'real'">
{{dist}}
</td>
<td ng-repeat="range in point.range track by $index" data-title="'Kombi'" sortable="'Kombi'">
{{range}}
</td>
</tr>
I found the mistake.
First the table data array had a structure like this: tableDATA = [{range:[],distances:[]}];
There was only one object at all times and only the arrays in this one object got filled.
I changed the table data array to: tableDATA = []; and per each click I fill the array with a new object like this: tableDATA.push({range:round+' km',distances:total+' km'});
Now it works fine :)
I want to show a list of elements on view asynchronously.
For example i have a method which returns promise and result will look like this.
var result = {
books: [
{name: 'The Kite Runner', author: 'Khaled Hosseini'},
{name: 'The Book Thief', author: 'Markus Zusak'},
{name: 'A Thousand Splendid Suns', author: 'Khaled Hosseini'},
]
}
Here is a method:
function getBooks(userId) {
return BookProduct.getBooksByUser.query({ id: userId }).$promise;
};
And then i have a method which invokes getBooks.
$scope.showBooks = function(userId) {
//some users id
var users_id = [1,2,3,4,5];
$scope.tables = [];
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
}
Thereafter i want to show it to user on a view using angular ng-repeat.
<div data-ng-repeat="table in tables">
<table>
<thead>
<tr>
<th>Name</th>
<th>Author</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="book in table.books">
<td>{{ name }}</td>
<td>{{ author }}</td>
</tr>
</tbody>
</table>
</div>
It works good but ng-repeat will work when function $scope.showBooks is executed and $scope.tables is already full. I am looking for more nice solution. I want to load books for user piecemeal on each iteration when method getBooks() invokes.
So in this part for example when the first iteration is processing and i get result of 2 elements, then i pass them to the view and user can see the first part. Next iteration i get for example other 3 elements, so i add them to previous result and pass to the view, so user can see 5 elements now, and so on. I know it will be almost insensibly for user but if my server responds slowly it will help me to be more user friendly.
for (i = 0; i < users_id.length; i++) {
getBooks(i).then(function(result){
$scope.table = {
books = result;
}
$scope.tables.push($scope.table);
})
}
I am not asking you HOW to implement it. I just want to know is it possible or not and where should i dig to know more, cause i tried some googling but without success.
Angular has a cycle in which it performs all scope functionality and after each cycle of "watches" it renders changes to the view. By default, Angular performs this automatically - for example simple change of variable in scope also rerenders view. But in some cases you may want to intercept because your operation didn't trigger the rerender.
For these cases, $apply and $digest are useful - more in documentation https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
I'm trying to bind a late calculated field to my ui with not much of success, This is my controller -
PaymentsController = ['$scope', 'Payment','Campaign', ($scope, Payment,Campaign)->
Payment.query().then((payments) ->
$scope.payments = payments
angular.forEach($scope.payments,(payment,index)->
payment.from = $scope.getFrom(payment)
)
The concept is simple, but the method getFrom returns a promise, when i get to this controller i load a list, the ui looks like this:
<table>
<tr>
<th ng-bind="'from'|i18n"></th>
<th ng-bind="'amount'|i18n"></th>
<th ng-bind="'to'|i18n"></th>
<th ng-bind="'description'|i18n"></th>
</tr>
<tr ng-repeat="payment in payments">
<td ng-bind="payment.from"></td>
<td>{{getAmount(payment)}}</td>
<td>{{getTo(payment)}}</td>
<td>{{getDescription(payment)}}</td>
</tr>
<tr ng-show="!payments.length">
<td ng-bind="'no_payments'|i18n"></td>
</tr>
</table>
nothing fency,
all fields are bind as expected except the from, according to controller i expect to see "asd"
but i see nothing.
I need to return a promise in this method but first I'm trying to make it work with simple code.
My problem was more conceptual I guess, I managed to fix this by making the controller control the view and not the view to control the controller, so instead of assigning a promise to the "from" field, I declared it when I had a value to put in, so my code looks like this:
angular.forEach($scope.payments, (payment, index)->
$scope.resolveFrom(payment)
)
$scope.resolveFrom = (payment)->
if payment.amount < 0
payment.from = "1"
else if payment.campaign_id
Campaign.get(payment.campaign_id)
.then((campaign)->
payment.from = campaign.name)))
payment.from = "unkown"
I'm not sure if this is the best practice, would appreciate comments/alternative solutions.