I am looking to order my list by a number of specific values on one property, and then some addtional properties once that is done.
The code I have is:
<tr ng-repeat-start="subcontractor in trade.SubContractors | orderBy:['Status':'PREF','Status':'APPR','Status':'LOGD','Status':'NIU','-AverageScores.AverageScore','-ProjectCount','SubcontractorName']">
Where the important bit (and the bit I can't get working) is:
'Status':'PREF','Status':'APPR','Status':'LOGD','Status':'NIU'
Is there a way of doing this in angular?
I would instead implement a handler to process data. Process subcontractors before it's set, running each element through a handler and assigning "sortValue" property to each one.
Then simply call orderBy using the sortValue property. This way you would decouple sorting data from displaying data. Though don't use a filter to do so, as it would be quite expensive resource-vise.
Something like
var statusSorting = ['PREF','APPR','LOGD','NIU'];
function sortContractors(models) {
var processed = [];
angular.forEach(models, function(model){
// logic to assign sortValue
var statusIndex = statusSorting.indexOf(model.status);
model.sortValue = statusIndex + 1;
});
return processed;
}
api.getData()
.then(function(data){
$scope.models = sortContractors(data);
});
// template
<tr ng-repeat="model in models | orderBy:'sortValue'">
You can then control priority by changing status position in the array and ordering desc/asc.
Alternately:
orderBy multiple fields in Angular
<tr ng-repeat="model in models | orderBy:['sortValue', 'contractorName']">
Your answer had a few non-required parts so I've tweaked it to give me the following:
function getStatusSortOrder(subcontractors) {
var statusSorting = ['PREF', 'APPR', 'LOGD', 'NIU'];
angular.forEach(subcontractors, function (model) {
statusIndex = statusSorting.indexOf(model.Status);
model.StatusSortValue = statusIndex;
});
}
Related
I have a list of items, which comes in unsorted, I use orderBy to sort by name alphanumerically.
<li class="ticker-li"
ng-repeat="ticker in tickers | orderBy:'ticker'"
ng-class="{'selected':ticker.selected}">
<div class="ticker"
ng-click="unselectAll(); ticker.selected = !ticker.selected;
selectTicker(ticker);
revealTickerOptions()">
{{ticker.ticker}}
</div>
Now in my controller this is how I'm currently setting the first items selected class:
var vs = $scope;
vs.tickers = data;
vs.tickers[0].selected = true;
^ This worked perfectly until I needed to add the orderBy so that items appear by alpha order:
I found this answer here, however it locks the first item to always have the class.
Modifying my code a bit, I was able to have other buttons gain that class on click, however the $first item still stayed with the class.
ng-class="{'selected':$first || ticker.selected}"
In my controller this is my unselectAll function, which doesn't work with 'selected':$first:
vs.unselectAll = function() {
for (var i = 0; i < vs.tickers.length; i++) {
vs.tickers[i].selected = false;
}
};
How should the code either in the markup or controller need to be updated to fix this issue?
Give this a shot, I'm not sure how it reads the $index on the sort by, but get rid of the $first thing and put this init statement in there.
<li class="ticker-li"
ng-repeat="ticker in tickers | orderBy:'ticker'"
ng-init="$index ? ticker.selected = false : ticker.selected = true"
ng-class="{'selected':ticker.selected}" ng-click="unselectFirst($index)">
I think this is a grey area between a hack or not, you aren't technically aliasing a property in the ng-init, but i think it is a fine line. The other solution would be sort the array in your controller, there is an example in the docs that sort on alphabetic order, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
I am using order by of Angular for sorting but I want to sort data based on three different fields, i.e success, in-progress and failed, without using any constant and variable directly from in-built function. Is there any way?
If you want you can call a function to sort your data as you want.
you can call following custom 'orderBy'
$scope.sort = function(column,reverse) {
$scope.persons = $filter('orderBy')($scope.persons,column,reverse);
};
'$scope.persons' - is your collection of data you need to order
'column' - name of the column you want to sort data
'reverse'- bool value to reverse the order
and you can call this 'sort' function from your View(HTML) as below.
data-ng-click="sort('name',nameReverse)"
you can pass array of fields to orderBy
<div ng-repeat="row in list | orderBy:['param1','param2']">
....
</div>
EDIT
to do it in javascript
$scope.sortedList = $filter('orderBy')(list,['param1', 'param2']);
EDIT
in smart table
function customSortAlgorithm(arrayRef, sortPredicate, reverse) {
//do some stuff
return sortedArray;
}
scope.globalConfig = {
sortAlgorithm: customSortAlgorithm
};
(post edited again, new comments follow this line)
I'm changing the title of this posting since it was misleading - I was trying to fix a symptom.
I was unable to figure out why the code was breaking with a $digest() iterations error. A plunk of my code worked fine. I was totally stuck, so I decided to make my code a little more Angular-like. One anti-pattern I had implemented was to hide my model behind my controller by adding getters/setters to the controller. I tore all that out and instead put the model into the $scope since I had read that was proper Angular.
To my surprise, the $digest() iterations error went away. I do not exactly know why and I do not have the intestinal fortitude to put the old code back and figure it out. I surmise that by involving the controller in the get/put of the data I added a dependency under the hood. I do not understand it.
edit #2 ends here.
(post edited, see EDIT below)
I was working through my first Error: 10 $digest() iterations reached. Aborting! error today.
I solved it this way:
<div ng-init="lineItems = ctrl.getLineItems()">
<tr ng-repeat="r in lineItems">
<td>{{r.text}}</td>
<td>...</td>
<td>{{r.price | currency}}</td>
</tr
</div>
Now a new issue has arisen - the line items I'm producing can be modified by another control on the page. It's a text box for a promo code. The promo code adds a discount to the lineItem array. It would show up if I could ng-repeat over ctrl.getLineItems().
Since the ng-repeat is looking at a static variable, not the actual model, it doesn't see that the real line items have changed and thus the promotional discount doesn't get displayed until I refresh the browser.
Here's the HTML for the promo code:
<input type="text" name="promo" ng-model="ctrl.promoCode"/>
<button ng-click="ctrl.applyPromoCode()">apply promo code</button>
The input tag is writing the value to the model. The bg-click in the button is invoking a function that will apply the code. This could change the data behind the lineItems.
I have been advised to use $scope.apply(...). However, since this is applied as a matter of course by ng-click is isn't going to do anything. Indeed, if I add it to ctrl.applyPromoCode(), I get an error since an .apply() is already in progress.
I'm at a loss.
EDIT
The issue above is probably the result of me fixing of symptom, not a problem. Here is the original HTML that was dying with the 10 $digest() iterations error.
<table>
<tr ng-repeat="r in ctrl.getLineItems()">
<td>{{r.text}}</td>
<td>...</td>
<td>{{r.price | currency}}</td>
</tr>
</table>
The ctrl.getLineItems() function doesn't do much but invoke a model. I decided to keep the model out of the HTML as much as I could.
this.getLineItems = function() {
var total = 0;
this.lineItems = [];
this.lineItems.push({text:"Your quilt will be "+sizes[this.size].block_size+" squares", price:sizes[this.size].price});
total = sizes[this.size].price;
this.lineItems.push({text: threads[this.thread].narrative, price:threads[this.thread].price});
total = total + threads[this.thread].price;
if (this.sashing) {
this.lineItems.push({text:"Add sashing", price: this.getSashingPrice()});
total = total + sizes[this.size].sashing;
}
else {
this.lineItems.push({text:"No sashing", price:0});
}
if(isNaN(this.promo)) {
this.lineItems.push({text:"No promo code", price:0});
}
else {
this.lineItems.push({text:"Promo code", price: promos[this.promo].price});
total = total + promos[this.promo].price;
}
this.lineItems.push({text:"Shipping", price:this.shipping});
total = total + this.shipping;
this.lineItems.push({text:"Order Total", price:total});
return this.lineItems;
};
And the model code assembled an array of objects based upon the items selected. I'll abbreviate the class as it croaks as long as the array has a row.
function OrderModel() {
this.lineItems = []; // Result of the lineItems call
...
this.getLineItems = function() {
var total = 0;
this.lineItems = [];
...
this.lineItems.push({text:"Order Total", price:total});
return this.lineItems;
};
}
The problem is that with each $digest cycle, a new array is returned (even if it contains objects with equal values, new objects are created).
To circumvent this, you could associate ngRepeat with a lineItems property and call getLineItems() only when something might have changed.
A possible implementation is the following:
<!-- The VIEW -->
<table>
<tr ng-repeat="r in ctrl.lineItems">...</tr>
</table>
/* The CONTROLLER */
.controller('myCtrl', function (OrderModel) {
this.orderModel = OrderModel;
this.lineItems = this.orderModel.lineItems;
this.reloadItems = this.orderModel.getLineItems;
// Initialization
this.reloadItems();
});
/* The SERVICE */
app.service('OrderModel', function () {
this.lineItems = [];
this.getLineItems = function () {
var total = 0;
this.lineItems.splice(0, this.lineItems.length);
...
for (var i = 0; i < 10; i++) {
total++;
this.lineItems.push({text: 'Order Total', price: total});
}
};
});
See, also, this short demo.
I'm using ng-repeat to create a list of entires and using a filter.
Is it possible to get the number of entries returned, like a length
data-ng-repeat="entry in entries | filter: { country_code : countryCode }"
It is little bit tricky, use ng-init:
ng-init="filter_len = (entries | filter: { country_code : countryCode }).length"
Example: http://jsfiddle.net/KJ3Nx/
As you know filter responsible to "filter" the input list and it returns filtered list where objects have the same structure. Otherwise you get digest cycle reentering that causes Exceptions (aka > 10 cycles).
1st way
Get length after filtering:
<pre>{{(entries| myfilter:types).length }}</pre>
See Example
2nd way
Use custom filter and get length from there.
iApp.filter('myfilter', function() {
return function( entries) {
var filtered = [];
angular.forEach(entries, function(entry) {
filtered.push(entry);
});
// here fetch list length
return filtered;
};
});
I suppose the following code will work for you:
<p>Filtered number: {{(entries|filter:{country_code:countryCode}).length}}</p>
<p>Total number: {{entries.length}}</p>
Inside my controller, I would like to filter an array of objects. Each of these objects is a map which can contain strings as well as lists
I tried using $filter('filter')(array, function) format but I do not know how to access the individual elements of the array inside my function. Here is a snippet to show what I want.
$filter('filter')(array, function() {
return criteriaMatch(item, criteria);
});
And then in the criteriaMatch(), I will check if each of the individual property matches
var criteriaMatch = function(item, criteria) {
// go thro each individual property in the item and criteria
// and check if they are equal
}
I have to do all these in the controller and compile a list of lists and set them in the scope. So I do need to access the $filter('filter') this way only. All the examples I found in the net so far have static criteria searches inside the function, they don't pass an criteria object and test against each item in the array.
You can use it like this:
http://plnkr.co/edit/vtNjEgmpItqxX5fdwtPi?p=preview
Like you found, filter accepts predicate function which accepts item
by item from the array.
So, you just have to create an predicate function based on the given criteria.
In this example, criteriaMatch is a function which returns a predicate
function which matches the given criteria.
template:
<div ng-repeat="item in items | filter:criteriaMatch(criteria)">
{{ item }}
</div>
scope:
$scope.criteriaMatch = function( criteria ) {
return function( item ) {
return item.name === criteria.name;
};
};
Here's an example of how you'd use filter within your AngularJS JavaScript (rather than in an HTML element).
In this example, we have an array of Country records, each containing a name and a 3-character ISO code.
We want to write a function which will search through this list for a record which matches a specific 3-character code.
Here's how we'd do it without using filter:
$scope.FindCountryByCode = function (CountryCode) {
// Search through an array of Country records for one containing a particular 3-character country-code.
// Returns either a record, or NULL, if the country couldn't be found.
for (var i = 0; i < $scope.CountryList.length; i++) {
if ($scope.CountryList[i].IsoAlpha3 == CountryCode) {
return $scope.CountryList[i];
};
};
return null;
};
Yup, nothing wrong with that.
But here's how the same function would look, using filter:
$scope.FindCountryByCode = function (CountryCode) {
// Search through an array of Country records for one containing a particular 3-character country-code.
// Returns either a record, or NULL, if the country couldn't be found.
var matches = $scope.CountryList.filter(function (el) { return el.IsoAlpha3 == CountryCode; })
// If 'filter' didn't find any matching records, its result will be an array of 0 records.
if (matches.length == 0)
return null;
// Otherwise, it should've found just one matching record
return matches[0];
};
Much neater.
Remember that filter returns an array as a result (a list of matching records), so in this example, we'll either want to return 1 record, or NULL.
Hope this helps.
Additionally, if you want to use the filter in your controller the same way you do it here:
<div ng-repeat="item in items | filter:criteriaMatch(criteria)">
{{ item }}
</div>
You could do something like:
var filteredItems = $scope.$eval('items | filter:filter:criteriaMatch(criteria)');