AngujarJS server-side pagination grand total items using $resource - angularjs

I'm trying to implement server-side pagination on an AngujarJS app but haven't figured out how to get the grand total (not the per-request total) items in a given JSON response without having to do an additional request:
$scope.books = [];
$scope.limit = 3;
$scope.offset = 0;
$scope.search = function () {
Books.query({ q: $scope.query, limit: $scope.limit, offset: $scope.offset },
function (response) {
$scope.books = response;
}
);
};
$scope.previous = function () {
if ($scope.offset >= $scope.limit) {
$scope.offset = $scope.offset - $scope.limit;
$scope.search();
}
}
$scope.next = function () {
if ($scope.offset <= $scope.limit) {
$scope.offset = $scope.offset + $scope.limit;
$scope.search();
}
}
I've been looking at great contributed directives such as ng-table and ng-grid which already implement this as well as other useful features but I just want to be able to implement this one functionality from scratch to really learn the basics.

Sounds like you are struggling with your api more than angular.
See the answer below for suggestions for the api.
What’s the best RESTful method to return total number of items in an object?

Related

AngularJS - View not updating with new array

I have an array which loads old data on page load.
However, this is not being loaded by angularJS.
It seems my controller is being called twice.
Here's the code
$scope.searchTerms = JSON.parse(APIDataService.loadLocalData('changedSearchTerm'));
var totalPages = JSON.parse(APIDataService.loadLocalData('oldData'));
var getOldDataOn = APIDataService.getOldData($scope.searchTerms, totalPages, 1)
.then(function(data) {
if(data.thumbpath.length > 0)
{
console.log('records found');
$scope.hasResults = true;
$scope.records = data.thumbpath;
} else {
$scope.hasResults = false;
}
},
function(data) {
console.log('Image retrieval failed.')
});
For some reason, console.log($scope.records) shows records, but Angular view is not being updated. I also tried $scope.$apply, but it's not working.
Please help.
Thank you in advance.

angularjs: invoking custom filter from within controller

I'm trying to manipulate some JSON data using a custom angular filter and I'm having difficulty when trying to invoke it from within my controller.
As you can see in this jsfiddle, the idea is to only return items from my json with a rating higher than 6, which should return two items: items 2 & 3.
This is my filter:
.filter('greaterThan', function() {
return function(a, b) {
return parseInt(a) > parseInt(b);
}
})
and I'm invoking it within my controller like so:
var greaterThanFilter = $filter('greaterThan'),
filteredItems = greaterThanFilter(data, {'rating': 6});
$scope.items = filteredItems;
I've tried to model my filter and the invocation on the built-in angular filters, so I'm not sure what I'm doing wrong.
You're not implementing the filter the way you think you are, it looks like you're sorting, but what?
This is probably more like what you are looking for, note the options parameter.
.filter('greaterThan', function() {
return function(data, options) {
var result = [];
data = data || [];
if (options.rating != undefined) {
for (let i = 0; i < data.length; i++)
if (data[i].rating > options.rating)
result.push(data[i]);
}
return result;
}
})
And in your controller, invoke like this
$scope.items = $filter('greaterThan')(data, {rating: 6});
I forked your fiddle, see this

Atmosphere and Angular JS how to

I'm an atmosphere & Angular newbie and I'm really struggling to find an answer to this! Maybe I'm asking the wrong question.
I am setting up notifications using Atmosphere. I can open the websocket and watch the updates happen if I post the API URL directly into my browser.
In Angular I have an ng-repeat loop, which I would like to run as each new update adds a new object to the websocket.
<li ng-repeat="notification in notifications track by $index">
I am using angular watch to check for updates, but it doesn't pick up the new objects being added to the array. Here is my code:
// notification alerts
$scope.notifications = [];
notificationsService.notificationAlerts().then(function success(response) {
var jsonStringArray = response.data.split('|');
$scope.notifications = $.map(jsonStringArray, function(n, i){
if (n !== ""){
return JSON.parse(n);
}
});
console.log('Connect', response);
});
$scope.$watch('notifications', function(newVal, oldVal){
console.log('Watch', $scope.notifications);
}, true);
Hopefully I've made myself clear, let me know if I need to elaborate, or if I'm asking the wrong question. Thanks!
OK, I managed to solve this, for anyone stumbling across it later. Here is the final JS:
// add number of notifications to ".notifications-number"
function updateNumberOfNotifications(){
var numberOfNotifications = $("ul.notifications-list li").not(".nocount").length;
if (numberOfNotifications < 1) {
$(".notifications-number, .notifications-list").addClass("hidden");
} else {
$(".notifications-number").html(numberOfNotifications);
$(".notifications-number, .notifications-list").removeClass("hidden");
}
}
// notification alert variables
$scope.notifications = [];
var socket = atmosphere;
var subSocket;
// subscribe
function subscribe() {
var request = {
url : "/service/notifier",
transport: 'long-polling'
};
request.onMessage = function (response) {
//console.log('response', response);
var jsonStringArray = response.responseBody.split('|');
// console.log('json string array', jsonStringArray);
$.each(jsonStringArray, function(index, elem){
if (elem != ""){
$scope.notifications.push(JSON.parse(elem));
console.log("object", JSON.parse(elem));
}
});
//$scope.notifications.push($scope.newNotification);
$scope.$apply();
updateNumberOfNotifications();
// console.log('$scope.notifications', $scope.notifications);
};
subSocket = socket.subscribe(request);
}
function unsubscribe(){
socket.unsubscribe();
}
// subscribe on load and update notifications
updateNumberOfNotifications();
subscribe();

How can I get changed filter text for each column in Angular ui grid?

I m using new angular uigrid. But I think documentation is really poor for searching. I did not find necessary infos for filtering. I will use server side filtering . SO I have to get changed filter text for each column. Please help me?
I tried this code. But probably it was for ng grid(old grid)
$scope.$watch('completedgridOptions.filterOptions.filterText', function (newVal, oldVal) {
if (newVal !== oldVal) {
console.log("Filter");
}
}, true);
if you want to check what is what:
$scope.gridApi.core.on.filterChanged($scope, function () {
var grid = this.grid;
$.each(grid.columns, function (index, column) {
switch (column.field) {
case 'externalIdentifier':
$scope.identifier = column.filters[0].term;
break;
case 'name':
$scope.name = column.filters[0].term;
break;
case 'status':
$scope.status = column.filters[0].term;
break;
....
....
....
}
});
//you have to move it by hand, if you are on the third page and filter, it will stay on the third page, even though you will not have any data there
grid.options.paginationCurrentPage = 1;
//use the timeout so that you don't get a db call at every key pressed
if (angular.isDefined($scope.filterTimeout)) {
$timeout.cancel($scope.filterTimeout);
}
$scope.filterTimeout = $timeout(function () {
getPage(); //this will call your data from the db
}, 500);
});
var getPage = function () {
itemsPerPage = paginationOptions.pageSize;
offset = (paginationOptions.pageNumber -1) * paginationOptions.pageSize;
getDataByFilter($scope.name, $scope.identifier, $scope.status, offset, itemsPerPage)
}
I am new using angular as well but try this:
$scope.gridApi.core.on.filterChanged($scope, function () {
var grid = this.grid;
for (var i = 0; i < objGrid.columns.length; i++) {
term = objGrid.columns[i].filter.term;
field = objGrid.columns[i].field;
console.log('Field: ' + field + '\nSearch Term: ' + term);
}
});
$scope.gridOptions.data = $scope.data;//initialiazing grid with data
$scope.gridApi.core.on.filterChanged($scope, function () {
var grid = this.grid;
//do your stuff here
});
Basically I was able to use the filterchanged feature on grid only after the grid gets initialized with data.
So put the snippet as suggested in the thread only after initializing grid with data.
I have used $timeout as below for my convinience.
$timeout(function () {
$scope.getData();//get your data from ajax call or from where ever your data is
$scope.assignDatatoGrid();//assign the data to grid or initialize the grid with data
$scope.$apply();
//this feature is available only after the grid gets populated with data
$scope.gridApi.core.on.filterChanged( $scope, function() {
var grid = this.grid;
});
}, 2000);
It worked for me
Thank you

Proper place for data-saving logic in AngularJS

App design question. I have a project which has a very large number of highly customized inputs. Each input is implemented as a directive (and Angular has made this an absolute joy to develop).
The inputs save their data upon blur, so there's no form to submit. That's been working great.
Each input has an attribute called "saveable" which drives another directive which is shared by all these input types. the Saveable directive uses a $resource to post data back to the API.
My question is, should this logic be in a directive at all? I initially put it there because I thought I would need the saving logic in multiple controllers, but it turns out they're really happening in the same one. Also, I read somewhere (lost the reference) that the directive is a bad place to put API logic.
Additionally, I need to introduce unit testing for this saving logic soon, and testing controllers seems much more straightforward than testing directives.
Thanks in advance; Angular's documentation may be… iffy… but the folks in the community are mega-rad.
[edit] a non-functional, simplified look at what I'm doing:
<input ng-model="question.value" some-input-type-directive saveable ng-blur="saveModel(question)">
.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{id: question.id, answer: question.value},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
}
}])
No, I don't think the directive should be calling $http. I would create a service (using the factory in Angular) OR (preferably) a model. When it is in a model, I prefer to use the $resource service to define my model "classes". Then, I abstract the $http/REST code into a nice, active model.
The typical answer for this is that you should use a service for this purpose. Here's some general information about this: http://docs.angularjs.org/guide/dev_guide.services.understanding_services
Here is a plunk with code modeled after your own starting example:
Example code:
var app = angular.module('savingServiceDemo', []);
app.service('savingService', function() {
return {
somethingOrOther: {
save: function(obj, callback) {
console.log('Saved:');
console.dir(obj);
callback(obj, {});
}
}
};
});
app.directive('saveable', ['savingService', function(savingService) {
return {
restrict: 'A',
link: function(scope) {
scope.saveModel = function(question) {
savingService.somethingOrOther.save(
{
id: question.id,
answer: question.value
},
function(response, getResponseHeaders) {
// a bunch of post-processing
}
);
}
}
};
}]);
app.controller('questionController', ['$scope', function($scope) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
}]);
The relevant HTML markup:
<body ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="saveModel(question)" />
</body>
An alternative using only factory and the existing ngResource service:
However, you could also utilize factory and ngResource in a way that would let you reuse some of the common "saving logic", while still giving you the ability to provide variation for distinct types of objects / data that you wish to save or query. And, this way still results in just a single instantiation of the saver for your specific object type.
Example using MongoLab collections
I've done something like this to make it easier to use MongoLab collections.
Here's a plunk.
The gist of the idea is this snippet:
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
Notes:
dbUrl and apiKey would be, of course, specific to your own MongoLab info
The array in this case is a group of distinct collections that you want individual ngResource-derived instances of
There is a createResource function defined (which you can see in the plunk and in the code below) that actually handles creating a constructor with an ngResource prototype.
If you wanted, you could modify the svc instance to vary its behavior by collection type
When you blur the input field, this will invoke the dummy consoleLog function and just write some debug info to the console for illustration purposes.
This also prints the number of times the createResource function itself was called, as a way to demonstrate that, even though there are actually two controllers, questionController and questionController2 asking for the same injections, the factories get called only 3 times in total.
Note: updateSafe is a function I like to use with MongoLab that allows you to apply a partial update, basically a PATCH. Otherwise, if you only send a few properties, the entire document will get overwritten with ONLY those properties! No good!
Full code:
HTML:
<body>
<div ng-controller="questionController">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
<div ng-controller="questionController2">
<h3>Question<h3>
<h4>{{question.question}}</h4>
Your answer: <input ng-model="question.value" saveable ng-blur="save(question)" />
</div>
</body>
JavaScript:
(function() {
var app = angular.module('savingServiceDemo', ['ngResource']);
var numberOfTimesCreateResourceGetsInvokedShouldStopAt3 = 0;
function createResource(resourceService, resourcePath, resourceName, apiKey) {
numberOfTimesCreateResourceGetsInvokedShouldStopAt3++;
var resource = resourceService(resourcePath + '/' + resourceName + '/:id',
{
apiKey: apiKey
},
{
update:
{
method: 'PUT'
}
}
);
resource.prototype.consoleLog = function (val, cb) {
console.log("The numberOfTimesCreateResourceGetsInvokedShouldStopAt3 counter is at: " + numberOfTimesCreateResourceGetsInvokedShouldStopAt3);
console.log('Logging:');
console.log(val);
console.log('this =');
console.log(this);
if (cb) {
cb();
}
};
resource.prototype.update = function (cb) {
return resource.update({
id: this._id.$oid
},
angular.extend({}, this, {
_id: undefined
}), cb);
};
resource.prototype.updateSafe = function (patch, cb) {
resource.get({id:this._id.$oid}, function(obj) {
for(var prop in patch) {
obj[prop] = patch[prop];
}
obj.update(cb);
});
};
resource.prototype.destroy = function (cb) {
return resource.remove({
id: this._id.$oid
}, cb);
};
return resource;
}
var dbUrl = "https://api.mongolab.com/api/1/databases/YOURDB/collections";
var apiKey = "YOUR API KEY";
var collections = [
"user",
"question",
"like"
];
for(var i = 0; i < collections.length; i++) {
var collectionName = collections[i];
app.factory(collectionName, ['$resource', function($resource) {
var resourceConstructor = createResource($resource, dbUrl, collectionName, apiKey);
var svc = new resourceConstructor();
// modify behavior if you want to override defaults
return svc;
}]);
}
app.controller('questionController', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What kind of AngularJS object should you create to contain data access or network communication logic?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('And, I got called back');
});
};
}]);
app.controller('questionController2', ['$scope', 'user', 'question', 'like',
function($scope, user, question, like) {
$scope.question = {
question: 'What is the coolest JS framework of them all?',
id: 1,
value: ''
};
$scope.save = function(obj) {
question.consoleLog(obj, function() {
console.log('You better have said AngularJS');
});
};
}]);
})();
In general, things related to the UI belong in a directive, things related to the binding of input and output (either from the user or from the server) belong in a controller, and things related to the business/application logic belong in a service (of some variety). I've found this separation leads to very clean code for my part.

Resources