Angularjs Search in large data set with pagination - angularjs

currently I use filter:query for searching within the data, something like this
<input type="text" ng-model="query">
<tr ng-repeat="thought in thoughts | filter:query">
<td>thought.title</td>
</tr>
It works well, if I load the complete json data at once.
Now my question is can I achieve server side search along with pagination as I don't want to load the complete data set at once?
One approach : For the current result set search can be performed normally using filters, if no results are found make a call to server requesting the another piece of data. Is this approach good ?

Well, assuming you have a API/CGI on your server which executes the searches and returns a subset (e.g. by using SQLs limit start,number) it shall not be that complicated to achieve this. When you start a new query you would set thoughts to a empty array and then make the first API call, e.g. returning 10 results. And then you could have a button or whatever mechanism to make the next API call, returning result 11-20. In your $http callback function you would then simply always append the data returned by the server to your array, so that new data is added at the end. So think of something like this (this is no actual tested code, just written down for the sake of this answer):
$scope.getdata = function() {
$http.post('/api/whatever',
{ query: $scope.query, startat: $scope.thoughts.length })
.success(function(response,status,headers,config){
$scope.thoughts.push.apply( $scope.thoughts, reponse.data );
});
$scope.search = function() {
$scope.thoughts = [];
$scope.getdata();
}
Search for: <input ng:model="query">
<button ng:click="search()">Search</button>
<button ng:click="getdata()">Get more results</button>

There's not going to be any way to do a client-side search unless you load all the data into the client with your first ajax request.
Server side search is probably gonna be your best bet.

Related

Sorting in Angular

I am building an app using Angular.
I have JSON that comes back from the server sorted by date.
I bind to this data in the view.
A user may change what data is displayed in the view. This kicks off a request and the view gets updated as it is bound to the JSON data changes.
When Angular gets this JSON data it then sorts it alphabetically.
I want to maintain the sort order that comes from the server.
I googled this a bit and found the following solution...
<div ng-repeat="key in notSorted(myData)" ng-init="data = myData[key]">
"notSorted" is a function on the controller...
$scope.notSorted = function(obj){
if (!obj) {
return [];
}
return Object.keys(obj);
}
This works fine initially. The data displays in the correct order.
However it breaks the binding. So when the user tries to change the data being displayed, the view does not get updated as notSorted does not see the binding change.
I can think of a few hacky ways around this such as manually firing the updates but I would like to do this the correct angular way.
Anyone know what that is?
Thanks
I believe you need this:
https://docs.angularjs.org/api/ng/filter/orderBy
It works sort of like this:
<div ng-repeat="item in items | orderBy:'key'">
Because of the way the data was coming back from the server and how I needed to iterate over that data I wasn't able to get any of the "orderBy" or filter solutions to work.
However I discovered that in Angular 1.4 they removed the default ordering (alphabetical/ascending) in favour of preserving the order the data was returned from the server...
http://jaxenter.com/angular-releases-1-3-update-1-4-beta-113906.html
I was using 1.3.x and simply updated to 1.4 and it worked OOTB.
Thanks

AngularFire: How to update scope without syncing to Firebase (i.e. local states, like "loading" or "selected")

I'm new to Firebase and AngularJS (and AngularFire), but am managing to work most things out... however this one's stumping me.
Situation:
I've got a server and a separate frontend. The frontend has NO WRITE PERMISSIONS for Firebase - it can only read it's own data (using a token provided by the server). The server has an API which the frontend utilises to make updates.
For the frontend to request an update to a particular item in a list, it needs to provide that item's Firebase ID to the server (along with whatever other information the API needs). The server will first verify that ID and then use it to update the correct Firebase data.
I've got AngularFire doing a three-way data binding for these actions, which is awesome!
Problem:
Lets say my Firebase structure is as follows:
actions: {
-JFeuuEIFDh: { // Firebase IDs for arrays
label: "One",
.
.
.
},
-JfuDu2JC81: {
"label": "Two",
.
.
.
}
I have the following HTML:
<div ng-controller"SuperController">
<ul>
<!-- key is the Firebase ID for the action, which is
required for my server to know which object to update -->
<li ng-repeat="(key, action) in actions">
<a ng-click="doAction(key, action)">action.label</a>
<!-- **Loading IS NOT and SHOULD NOT be stored in Firebase,**
it's simply a local state which AngularJS should manage -->
<p ng-hide="!action.loading">Loading...</p>
</li>
</ul>
</div>
doAction looks something like this:
$scope.doAction = function(key, item) {
$scope.actions[key].loading = true;
var onComplete = function () {
$scope.actions[key].loading = false;
}
// Calls to the server, etc...
.
.
.
}
I'm using $scope.actions[key].loading to provide a local state so the "Loading..." paragraph will appear when the user initiates doAction, and disappear when the doAction logic completes.
However, because AngularFire has set up a three-way data binding, it tries to save that change to the database, which fails because the client does not have permission to write!
I don't want it to save loading to the database! It's there simply to update that paragraph's ng-hide - it shouldn't be persistent and there's no way this would justify providing write permission to the client.
So what am I supposed to do? I can't think of any way to update the scope without firing off the three-way binding... Am I thinking of this the wrong way?
EDIT
Nested deep in the AngularJS documentation, under $FirebaseObject.$bindTo, was this:
use a variable prefixed with _, which will not be saved to the server, but will trigger $watch().
However when I used $scope.actions[key]._loading instead, the same problem still occurred. There was no apparent difference.
I couldn't find a clean solution to the problem.
use a variable prefixed with _, which will not be saved to the server, but will trigger $watch().
This wasn't actually implemented in code. I was considering submitting an implementation myself but it wasn't as simple as I hoped. Even after toJSON stopped returning _'d variables, it still tried to save the (unchanged) JSON to Firebase, so you'd have to fix it earlier somehow... I didn't go there.
To solve the problem I used AngularFire's $asArray instead of $asObject. The array is READ ONLY. Firebase won't try to sync any changes unless you call special functions. In my case, this works, however in other cases it might not be sufficient.
I had to change a bit my templating to work with an array instead of an object since a numerical key was now being provided instead of the actual key being used in Firebase. I converted the numerical key to the proper one with: actions.$keyAt(parseInt(key)).
It was a mess.. but it'll get me through for now.
I am having same issue. but this seems to fix it.
https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject-bindtoscope-varname
If $destroy() is emitted by scope (this happens when a controller is > destroyed), then this object is automatically unbound from scope. It can > also be manually unbound using the unbind() method, which is passed into ? > the promise callback.
//Setup synced array as usual
$scope.syncedArray = $firebaseArray(ref);
//call unbind on the variable
$scope.syncedArray.unbind();
//reorder, filter and maniuplate arrays as usual and when done call .bind() on it.

Angular CRUD, update view when backend/database changes ($resource and REST)

I am currently making an application in angular which does this:
(On page load) Make an api call in angular controller (to symfony2 end point) to get: items.
$scope.items = ItemsService.query(function(data){
$scope.loading = false;
}, function(err){
$scope.loading = false;
});
items is an array containing many item objects.
Each item contains parameters e.g. item.param1 item.param2.
I have built it in a similar way to this tutorial:
http://www.sitepoint.com/creating-crud-app-minutes-angulars-resource/
i.e. The angular controller calls a service which calls the (symfony2) backend api endpoint.
The endpoint passes back items which is gets from a database. Items are then put into the view using ng-repeat (item in items).
This all works fine.
Now, I have a button (in the ng-repeat) which effectively causes a PUT request to be made to (another symfony2 endpoint), thus updating item.param1in the database. This also happens in the tutorial I linked to.
The problem is that (in my application and in the tutorial) I have to again make an api call which updates ALL the items, in order to see the change.
I want to just update the data in the view (immediately) for one object without having to fetch them all again.
i.e. something like:
$scope.items[4] = Items.get({id: item.id}, function(){});
Except the application array key isn't known so I cant do that.
(so something like: $scope.items.findTheOriginalItem(item) = Items.get({id: item.id}, function(){});.
Another possible solution (which seems like it may be the best?). I have looked here:
http://teropa.info/blog/2014/01/26/the-three-watch-depths-of-angularjs.html
And tried doing the equality $watch: $scope.$watch(…, …, true);. Thus using watch to see when the item sub-array is updated. This doesn't seem to update the data in the view though (even though it is updating in the database).
Any advice on the best way of doing this (and how to do it) would be great! Thanks!
Essentially the button click should use ng-click to execute a function and pass the item to that function. Example:
...ng-repeat="item in items"...
<button type="button" ng-click="updateItem(item)">Update</button
...
Then in the function you have the exact item that you want to update. If you are using $resources, it would be something like:
$scope.updateItem = function(item) { item.$update(...); };
Unless I didn't understand you

AngularJS: apply function after model loads

I print a dataset i obtain from a service in a list. That's ok.
So, i have two functions, *paint_other_avatars()* and *paint_more_participants()*, (they are http calls) in each item to get some realated data.
My problem is AngularJS won't render the list until all the data is fetched, so the page takes very much to load. I want to avoid that delay.
Initially, I planned enhance my SQL query to get all the needed data with a sole call, but now i think so many calls aren't so bad if i would do them asyncronously or render the list before this secondary calls.
I know one of my problems is setting the calls in ng-init(), but i don't know any directive like ng-after()
This is my code simplified:
<li ng-repeat="plan in plans | orderBy:get_timing" ng-animate=" 'animate' " ng-class="status(plan)">
<div class="desc_plan">
<span class="gris_24">{{plan.title}}</span>
</div>
<div class="asistentes">
<span id="other_avatars_{{plan.id}}" ng-init="paint_other_avatars(plan)"></span>
<span id="more_participants_{{plan.id}}" ng-init="paint_more_participants(plan)" class="asistentes_mas"></span>
</div>
</li>
EDIT for j_walker_dev:
hmmmm I am trying your solution but i have found a problem
i have
$scope.plans = Plan.query({token: token});
i guess this type of calls are asynchronous, so if i put
angular.forEach($scope.plans, function(plan) {
$scope.paint_other_avatars(plan);
$scope.paint_more_participants(plan);
});
the program is not entering in the forEach because it has not time to do it. maybe so?
For each iteration in the ng-repeat you are making two separate http calls? i think your problem lies there. Usually making any kind of web calls in a loop is a bad practice idea. Huge performance hit.
I suggest first to figure out a better design pattern to get the data without making separate calls for each. But if you must you could separate the requests from the view layer into your javascript controller.
What i mean is make your initial call to get plans. And then do a for loop over them in javascript and call paint_other_avatars and paint_more_participants in that loop. This way the async calls have no relation to the template rendering and does not slow down, once the plans load, your html will render. While in the background you are making your other calls for paint_other_avatars and paint_more_participants.
$scope.$watch('plans', function(newValue, oldValue) {
if (newValue.length) {
_.each(plans, function(plan) {
paint_other_avatars(plan);
paint_more_participants(plan);
})
}
})
I dont know what your two function calls are doing, but will this work in making your template load faster?
To
well, fortunately seems query() function accepts callback.
so i solved it this way
Plan.query({token: $cookies.ouap_token}, function(result){
$scope.plans = result;
angular.forEach($scope.plans, function(plan) {
$scope.paint_other_avatars(plan);
$scope.paint_more_participants(plan);
});
});
on the other hand, I am not sure this way is faster :/

angularjs - $http post reqest in queue one by one

I have hundred rows in my view. I would like to process all those rows one by one.
Every row has status
Not Complete (by default)
Complete
Processing
I would like to change status of each row before and after finish processing in controller.
Before finishing I would like to change status to processing and after finishing, I like to change complete.
<tr ng-repeat="item in pagedItems[currentPage] | orderBy:sortingOrder:reverse ">
<td>{{item.id}}</td>
<td>{{item.fname}}</td>
<td>{{item.lname}}</td>
<td>
<span ng-class="class{{item.id}}"></span>
</td>
</tr>
in controller I have
$scope.startSending = function() {
$scope.filteredItems.forEach(function(entry) {
// Throwing Error! How can dynamically get value of scope variable
$scope.'class'+entry.id = 'icon-eye-open';
});
};
Please advise how can I get value of dynamical named variable from $scope above in controller.
I would like to update my database as well, If I add $http in foreach loop.
$http.post('/someUrl', data).success(successCallback);
it will send all 100 requests all together. Can I execute each request one after another in queue?
I want to send request in queue, it should not send second request before completion of first request etc...
any idea?
Thanks
With respect to Please advise how can I get value of dynamical named variable from $scope above in controller. the answer is Javascript and not AngularJS related. Just do:
$scope['class' + entry.id] = 'icon-eye-open';
With respect to queueing the $http requests, you want to check out the various asynchronous flow control solutions. I've used in a Node.js app caolan's async and it's very nice. You can find it in github and a fine tutorial is available here. I believe that in your case you need a forEachSeries.
(Please try to limit each post to 1 question or if not possible at least to N related questions.)

Resources