Referring to outer scope variables in orderBy inside ng-repeat - angularjs

I'd like to have an ng-repeat that's sorted by a value in some external lookup table.
For example, suppose I have a list of items with an itemType for each item. I have a separate itemTypePriorities lookup table in the scope, such that itemTypePriorities[someItemType] gives the priority for a given item type. Now, I'd like to display items in order of their priorities. I am trying the following:
<div ng-repeat="item in items | orderBy:'itemTypePriorities[item.itemType]'>
But it doesn't seem to work - my guess is that it's interpreting the orderBy expression in the context of item, i.e. it's trying to order by the nonsensical expression item.itemTypePriorities[item.itemType].
Is there a way to rewrite the ng-repeat to make it wok correctly, without having to modify the items or itemTypePriorities arrays in any way?

You can use of course the orderBy filter but I think you will need to create a custom function to orderit.
For example:
<div ng-repeat="item in items | orderBy:myFunction>
Where myFunction is declared somewhere in your controller as:
$scope.myFunction = function(el){
//el is the current element under evaluation
}
And should return a value that will be compared with <, === and > with the other. In your case the function can be:
$scope.myFunction = function(el){
return itemTypePriorities.indexOf(el);
}
And that should work

Related

ng-repeat one-time binding inheritance?

If I have an ng-repeat directive iterating through a list of lists via a one-time binding, does a nested ng-repeat through each of the inner lists also need a one-time binding, or is that redundant?
<div ng-repeat="list in ::$ctrl.lists">
<span ng-repeat="item in list">{{item}}</span>
</div>
Do I need to change the inner ng-repeat to item in ::list? $ctrl.lists will never change in any manner.
It is specify nowhere that is is inherit, event if you have bind the array, object inside the array can still change, so it is better to put '::' for the span loop too.
http://jsfiddle.net/vw2fjxys/64/
var a = [1,2,3]
setTimeout(function(){
console.log(2)
a.length = 2
$scope.$apply();
},3000)
$scope.lists = [a];
You can see on the exemple after 2 second that with :: it is not recalculated for the inner loop but without it is.

Sorting ng-repeat with alternate ordering

I am trying to sort a client list using ng-repeat in Angular so that the column with ratings "Hot", "Warm", and "Cold" presented in that order. Currently when it sorts the Rating column it puts them in alphabetical order of "Cold", "Hot", and "Warm". How would I sort it non-alphabetically like that? Is it something with using a custom directive to create a custom attribute related to the rating value and then using "track by"?
Here is a CodePen with a working example of how it sorts alphabetically:
http://codepen.io/MattSchultz/pen/VeBpaQ
<li id="listCont" ng-repeat="client in clients | orderBy:col:reverse as results" ng-class-even="'even'">
<ul class="clients">
<li>{{client.Name}}</li>
<li>{{client.AnnualRevenue | currency}}</li>
<li ng-class="classy('{{client.Rating}}')">{{client.Rating}}</li>
</ul>
</li>
If you pass a string (i.e. an object property name) as the first argument to the orderBy filter, as you are doing in your example, it will attempt to sort alphabetically. To change this behavior, you need to pass a custom sort function instead.
For example:
function ratingSort(val) {
return ['Cold', 'Warm', 'Hot'].indexOf(val.Rating);
};
This function returns a number between -1 (i.e. val.Rating value is not in the array) and 2 (i.e. 'Hot', which is at index 2 in the array). The number returned is used to determine the sort order.
Here is a working example: CodePen

AngularJS - original index of an object within filtered ng-repeat

I am using a nested ng-repeat and a filter on a object. The first ng-repeat is filters to the headerId in a gapHeader object. The second ng-repeat filters gapSection, sectionId to the corresponding headerID.
I have an edit page which is within a separate modal window. The purpose is to edit content corresponding to the headerID & sectionID of the sub-object) This also has a separate control. Data is shared through a service.
My problem I have a button for each gapSection sub-object, which opens the edit page modal, when I pass the $index value for the current section within each section to the service, I get the $index only corresponding to the second ng-repeat? For example, if I click the button within the 2 ng-repeat on gapSection (headerId:2, sectionId:2), I get an $index of 1. I require an $index of 2 which corresponds the sub-object position within gapSection.
Is it possible to pass the true $index which corresponds to the $index defined in the original un-filtered object of gapSection? Appreciate any comments on this and thank you!
Object:
var data ={
gapHeader:[{headerId:1,name:"General Requiremets",isApplicable:true},
{headerId:2,name:"Insurance",isApplicable:false}],
gapSection:[{headerId:1,sectionId:1,requirement:"A facility is required to have company structure",finding:null,cmeasures:null,cprocedures:null,personResp:null,isAction:null},
{headerId:2,sectionId:1,requirement:"Organisation must have public liablity",finding:null,cmeasures:null,cprocedures:null,personResp:null,isAction:null},
{headerId:2,sectionId:2,requirement:"Facility must hold workers compensation insurance",finding:null,cmeasures:null,cprocedures:null,personResp:null,isAction:null}]
};
If you need the true index you do not even need to pass the $index property, just pass the object and get the index from the original list.
i.e
$scope.gapSectionFn = function(obj){
var idx = data.gapSection.indexOf(obj);
}
Also it is not clear your issue could really be a nested ng-repeat issue, because according to you gapSection is the inner ng-repeat and you are invoking the call from inner ng-repeat and in need of gapSection's index. It should just be available, but the presence of a DOM filter will just reorg the items and its index which you can also get by doing an ng-init, i.e on the view ng-init="actIndex=$index" and use actIndex.
If you are trying to access parent ng-repeat's index then, ng-init is more appropriate than $parent.$index. Since ng-init is specially designed for that., on the parent ng-repeat you would write ng-init=""parentIndex=$index" and use parentIndex.

angular ng-repeat skip an item if it matches expression

I'm looking for a way to basically tell angular to skip an item in an ng-repeat if it matches an expression, basically continue;
In controller:
$scope.players = [{
name_key:'FirstPerson', first_name:'First', last_name:'Person'
}, {
name_key:'SecondPerson', first_name:'Second', last_name:'Person'
}]
Now in my template I want to show everyone that doesn't match name_key='FirstPerson'. I figured it has to be filters so I setup a Plunkr to play around with it but haven't had any luck. Plunkr Attempt
As #Maxim Shoustin suggested, the best way to achieve what you want would be to use a custom filter.
But there are other ways, one of them being to use the ng-if directive on the same element were you put the ng-repeat directive (also, here's the plunker):
<ul>
<li ng-repeat="player in players" ng-if="player.name_key!='FirstPerson'"></li>
</ul>
This may present a minor disadvantage from an estetical perspective, but has a major advantage that your filtering could be based on a rule that is not as tight coupled to the players array and that can easily access other data in your app's scope:
<li
ng-repeat="player in players"
ng-if="app.loggedIn && player.name != user.name"
></li>
Update
As stated, this is one of the solutions for this kind of problem and may or may not suit your needs.
As pointed out in the comments, ng-if is a directive, which actually means that it might do more things in the background than you might expect.
For example, ng-if creates a new scope from it's parent:
The scope created within ngIf inherits from its parent scope using prototypal inheritance.
This usually doesn't affect the normal behaviour but in order to prevent unexpected cases, you should keep this in mind before implementing.
I know this is an old one, but in case someone would look for another possible solution, here is another way to solve this - use standard filter functionality:
Object: A pattern object can be used to filter specific properties on
objects contained by array. For example {name:"M", phone:"1"}
predicate will return an array of items which have property name
containing "M" and property phone containing "1". ... The predicate
can be negated by prefixing the string with !. For example {name:
"!M"} predicate will return an array of items which have property name
not containing "M".
So for the TS example something like this should do:
<ul>
<li ng-repeat="player in players | filter: { name_key: '!FirstPerson' }"></li>
</ul>
No need to write custom filters, no need to use ng-if with it's new scope.
You can use custom filter when you implement ng-repeat. Something like:
data-ng-repeat="player in players | myfilter:search.name
myfilter.js:
app.filter('myfilter', function() {
return function( items, name) {
var filtered = [];
angular.forEach(items, function(item) {
if(name == undefined || name == ''){
filtered.push(item);
}
/* only if you want start With*/
// else if(item.name_key.substring(0, name.length) !== name){
// filtered.push(item);
// }
/* if you want contains*/
// else if(item.name_key.indexOf(name) < 0 ){
// filtered.push(item);
// }
/* if you want match full name*/
else if(item.name_key !== name ){
filtered.push(item);
}
});
return filtered;
};
});
Demo Plunker

How can I filter to a single item in an array bing to it via an angular expression in markup?

I have a single element that I want bound to a single item in an array and ng-repeat doesn't seem applicable.
How can I do something like the following to bind to a single item in an array
<p class="bottomline">{{vehicle.Taglines[0].Tagline | $filter:{MarketId:$scope.MarketId}}</p>
Could you try this:
{{ (vehicle.Taglines | filter: {MarketId: MarketId})[0]["Tagline"] }}
Note, filter not $filter! And you have missed a bracket after the filter object argument!
I don't think it's possible but you can always write that logic in the Controller (and avoid putting so much logic in the template)
module('yourApp', []).controller(['$scope, $filter', function Controller($scope, $filter){
$scope.$watch('MarketId', function(marketId) {
$scope.tagLineFound = $filter('filter')($scope.vehicle.Taglines, marketId)[0];
});
}]);
HTML
<p class="bottomline">{{tagLineFound.Tagline}}</p>

Resources