Using filtering, ordering, and pagination together efficiently with AngularJS - angularjs

I have been implementing the ability to filter a table based on different fields and at first it seemed fairly straight forward, but now things are getting more complex and I want to make sure I am utilizing Angular's Digest cycle effectively.
Right now, when the user first hits a view and the controller gets instantiated, I call the API and GET an array of data from the server. I call this data a dataSet. This is the raw, unfiltered array of data.
Then on the page there are two different ways to filter, a search bar (<input>) and an actual filter menu where the user can toggle different values On/Off and have the table update accordingly in real-time. At first I thought it would be simple enough to place the filter logic inline inside of ng-repeat using a pipe, however this doesn't work with my pagination because the pagination continues to reflect the dataSet and not the filtered data. This led my HTML to look like
<tr ng-repeat="mailbox in model.IndividualMailboxes.filtered | orderBy:model.IndividualMailboxes.ordering.predicate:model.IndividualMailboxes.ordering.reverse | paginate:(model.IndividualMailboxes.currentPage - 1) * model.IndividualMailboxes.resultsPerPage | limitTo:model.IndividualMailboxes.resultsPerPage">
This then led me to creating a second array that I call filtered. I basically filter the dataSet array into this new filtered array. Then my ng-repeat and pagination both use the filtered set. This also forced me to place the actual filter in the controller and use filterFilter(arr, term) to filter the dataSet in 2 stages
function filterIndividualMailboxes() {
var verifiedList = [], searchList = [];
verifiedList = filterFilter($scope.model.IndividualMailboxes.dataSet, { Valid : $scope.model.IndividualMailboxes.showVerified });
searchList = filterFilter(verifiedList, $scope.model.IndividualMailboxes.search);
$scope.model.IndividualMailboxes.filtered = searchList;
}
The above function simply filters the dataSet by our filtering options (there is only 1 in this case) and then takes that set and filters it based on the search term that the user can input.
The problem with this implementation is it doesn't seem to fit into the digest cycle efficiently as I am having to call filterIndividualMailboxes() whenever the data is changed in any way. The easiest way to implement that was to just $watch() the entire model like this
$scope.$watch('model.IndividualMailboxes', function(){
filterIndividualMailboxes();
}, true);
But I am afraid that this isn't very efficient and not the most fluid way to deal with filtering in Angular.
Does anyone know of a more "Angular" way of doing this that fits right into the digest cycle?

Related

Need a way to query MongoDB collection, and ngRepeat beginning at specific point

is there a way to query a MongoDB collection, return the results, use an AngularJS ng-repeat to iterate through the results, BUT BEGIN the iteration at a specific position in the results, somewhere in the middle for example?
I am currently returning a query to an angular view; a category of materials. Then i have my view set up to paginate(ng-repeat) through the results. However, no matter what material I click on to bring me into the view (from a different view), the ng-repeat always starts at the beginning of the materials list, rather than on the material i clicked. Any thoughts?
I do not believe the mongodb part has anything to do with the actual question but ill give you 2 options:
The quick way:
<div ng-repeat="item in items" ng-show="$index >= myCtrl.startPoint">
While this will do the trick you might want to do the ng-show expression with a filter. In addition you might run into performance issues. You should also use the track by feature.
A better way would be to have a filteredData model for the ng-repeat and do the correct filtering once per action. You would have to make sure yourself that the model is updated on every data change and every user input.
There are many choices in between these 2 options but what to use depends on your needs. For instance - does user input change frequently? Is your data updated frequently? etc.

How to correlate view order (using orderBy) with model order

I have an obviously simple but yet challenging problem in my AngularJS app:
When using orderBy on the view I loose the correlation between the order of the related model and the view order.
What I want to do: My view is a table. I want to set a highlight class for the selected row and I want to use cursor up/down keys to move this highlight.
I tried to decouple $index from the tracking by using track by currentDocument.objectid, but that doesn't seem to work.
How can I correlate the currently highlighted row in the view with the currently highlighted element of the related model in a way that I can use -1 or +1 with the cursor keys?
As the order of the data is important to your business process, it makes sense to make the order part of your actual model. If you read the last example of orderby on the angular api (https://docs.angularjs.org/api/ng/filter/orderBy), you will notice they achieve this very thing. Basically, instead of binding orderby directly to some model value, create your own order function and manually order your model.
I have created a plunker here: http://plnkr.co/edit/oM97ZTAIHTjI6YFiGDwI?p=preview Just click on the persons name to see the example (and reorder the list and try again).
Basically you create a manual sort function like this:
$scope.order = function(predicate, reverse) {
$scope.friends = orderBy($scope.friends, predicate, reverse);
};
Which actually reorders the model itself, rather than just the view array. You can call it however you like. Then you can simply pass $index from the view, and it will correlate correctly to the view order.

Sorting and Filtering a collection in a Paged ListBox

I have a database with 10,000 items, to which you can add and remove while the app is running.
I have a ListBox that displays at most 100 items, and supports paging.
You can filter and sort on the 10,000 items, which needs to be immediately reflected in the listbox.
I have a button that randomly selects an item as long as it passes the filters.
What is the best set of collections/views to use for this kind of operation?
So far, my first step will be to create an ObservableCollection of ALL items in the database which we will call MainOC.
Then create a List of all items that match the filter by parsing MainOC which we will call FilteredList.
Then create a ListCollectionView based on the above List that holds the first 100 items.
CONS:
You have to recreate the ListCollectionView every time a sort operation is applied.
You have to recreate the ListCollectionView every time you page.
You have to recreate the ListCollectionView every time a filter is changed.
You have to recreate the ListCollectionView every time an item is added or removed to MainOC.
Is there a better approach that I am missing?
For example, I see that you can apply filters to a ListCollectionView. Should I populate my ListCollectionView with all 10,000 items? But then how can I limit how many items my ListBox is displaying?
Should I be doing my filtering and sorting directly against the database? I could build FilteredList directly off the database and create my ListCollectionView based off that, but this still has all the cons listed above.
Looking for any input you can provide!
This is a problem which is easily solved using DynamicData . Dynamic data is based on rx so if you are not familiar with the wonderful Rx I suggest you start learning it. There is quite a bit of a learning curve but but the rewards are huge.
Anyway back to my answer, the starting point of dynamic data is to get some data into a cache which is constructed with a key as follows
var myCache = new SourceCache<MyObject, MyId>(myobject=>myobject.Id)
Obviously being a cache there are methods to add, update and remove so I will not show those here.
Dynamic data provides a load of extensions and some controllers to dynamically interrogate the data. For paging we need a few elements to solve this problem
//this is an extension of observable collection optimised for dynamic data
var collection = new ObservableCollectionExtended<MyObject>();
//these controllers enable dynamically changing filter, sort and page
var pageController = new PageController();
var filterController = new FilterController<T>();
var sortController = new SortController<T>();
Create a stream of data using these controllers and bind the result to the collection like this.
var mySubscription = myCache.Connect()
.Filter(filterController)
.Sort(sortController)
.Page(pageController)
.ObserveOnDispatcher() //ensure we are on the UI thread
.Bind(collection)
.Subscribe() //nothing happens until we subscribe.
At any time you can change the parameters of the controllers to filter, sort, page and bind the data like follows
//to change page
pageController.Change(new PageRequest(1,100));
//to change filter
filterController.Change(myobject=> //return a predicate);
//to change sort
sortController .Change( //return an IComparable<>);
And as if by magic the observable collection will self-maintain when any of the controller parameters change or when any of the data changes.
The only thing you now have to consider is the code you need for loading the database data into the cache.
In the near future I will create a working example of this functionality.
For more info on dynamic data see
Dynamic data on Github
Wpf demo app

How can I bind a directive to a column cell in angular-ui-grid?

First, this is what I'm trying to do: I am using ngResource to grab an array of data from an API and setting the ui-grid's data to the array. One of the columns is an ID that has no meaning to the user. I would like to use another API to look up the meaning of that ID and get the human-friendly result of that. Before using ui-grid, I was simply using a directive that took that ID and set the element's text as the return result from the secondary API.
I know that it's possible to bind a column dynamically to data in that row's object, having looked at: http://ui-grid.info/docs/#/tutorial/106_binding, but it appears that this is limited to binding to the data in $scope or in the grid's data (such as in the example). I also see that there is a cellClass options method which allows conditional setting of the cell's class. However, I do not see any good options for intercepting the element in the cell and replacing it with a result of my choosing.
I am wondering what the best way to do this is. I've tried using ngResource's transformResponse but the array is too large to make a blocking event. As well, I've tried to inject the html for the directive into the field's return function, i.e. field: transformToHtml() but only the raw string shows, not the rendered html.

Add a "permanent" filter to a store, until I manually call clearFilter

I'm using a store to fetch the specializations of all hikers (so hiker has many specializations). However, I have a detail window where this specializations are added/removed/shown ony for currently selected hiker (yea, it's a detail window).
My problem here is that my store fetch data for all hikers, but I want it to show, when detailed window is up, only data for given hiker. Notice also that I'm showing data in data-grid, so the user possibly can add filters and I noticed that if I add a filter with store.filter({...}) and user add a filter with data-grid, my filters are removed (so basically they are useless.
Which approach should I use? Do you have any suggestion? I were thinking about 1 store for each hiker, but I don't like this solution.
Edit 1:
I also noticed that I can't create a filter in the same way as data-grid builds them:
Ext.create('Ext.util.Filter', {
type: 'numeric',
comparison: 'eq',
field: 'hiker_id',
property: 'hiker_id',
value: me.hiker.get('id'),
root: 'data'
})
Which is boring, because I imnplemented on server side a functionality that parses the grid filters already.
We just give our filters in json format to the store's extra params. And parse that back-end. The extra params stay the same while paging or refreshing the grid.
grid.getStore().getProxy().extraParams = { filter: yourFilter };
-How- are your users doing the filter?
store.filter accepts both arrays and functions, so you can do quite a bit with it, without actually juggling the data on the server. (eg manage an array that is being passed to the filter, test against an object somewhere, or whatever)
Permanent filter? Not so much, but since you can add multiple filters etc, it is relatively trivial to keep track of what filters are in place.
http://docs.sencha.com/ext-js/4-1/#!/api/Ext.data.Store-method-filter

Resources