angular ng-repeat skip an item if it matches expression - angularjs

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

Related

Referring to outer scope variables in orderBy inside ng-repeat

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

Default value with ng-options after filter

I have a <select> box with options driven from a filter, based on the value of another field in the model (another <select> field). If, after filtering the list, there's only one option to display, I want to make it the selected option. ng-init seems like a starting point, but it's also recommended by the Angular team as something to not use.
SELECT:
<select
tabindex="5"
class="form-control"
ng-model="vm.transaction.um"
ng-options="um.name for um in vm.ums | measuresForItem:vm.transaction.item">
</select>
Basically what you want to do is to change some model value (vm.transaction.um) based on some other model value (filtered vm.ums). There is $watch() function that does exactly this thing. Try
$scope.$watch('(vm.ums | measuresForItem:vm.transaction.item).length', function(newVal) {
if (newVal == 1)
$scope.vm.transaction.um = (<get filtered result here>)[0].name;
});
Actually ng-init is not that bad for such sort of tasks, but it executes only once, and it might be not suitable for dynamic filters or any kind of deferring.
Just set vm.transaction.um (the member you assigned to ng-model) in your controller:
if(this.vm.ums.length === 1) {
this.vm.transaction.um = this.vm.ums[0];
}

How to update $index when list changes in angular

I'm trying to implement a keyboard navigation in a multi-level List.
Therefor i try to give every Item in the List a unique ID, like 5.2.1(category.item.subitem).
I already tried a lot of stuff, like ng-init the index to a variable on ng-repeat, an other approaches using directives, but all had the problem so far that when i change the list (delete items for example) my Custom Index doesn't update!
I made a simple Plnkr here:
You can delete an item or subitem and see that the "real" index and the customindex don't relate.
http://plnkr.co/edit/BsRCNAqM7jWkeAJ9rkLN?p=preview
at the moment i have a custom directive called customIndex that gets the Index as attribute.
<li ng-repeat="subitem in item.subitem" custom-Index="$parent.$index+'.'+$index">
and inside the directive i simply $eval the attribute:
.directive('customIndex', function(){
return{
restrict:'A',
link: function(scope, el, attrs){
scope.myIndex = scope.$eval(attrs.customIndex);
}
}
})
But this, like all other solutions i tried, doesn't work.
I think this must be a common kind of problem.
Does anyone have any suggestions for me?
THANKS
Markus
I think you overcomplicated yourself. Try only this piece of markup and no need for custom directive or something else:
<ul>
<li ng-repeat="item in data">
"Real"-index:{{$index}} | myIndex:{{myIndex = $index}}} | Item:{{item.itemName}} <button ng-click="data.splice(data.indexOf(item),1)">Del</button>
<ul>
<li ng-repeat="subitem in item.subitem">
"Real"-index:{{$index}} | myIndex:{{myIndex = $parent.$index+'.'+$index}} | Subitem:{{subitem}} <button ng-click="item.subitem.splice(item.subitem.indexOf(subitem),1); ">Del</button>
</li>
</ul>
</li>
</ul>
ng-init will never work for you because it is executed only when directive is compiled. With custom directive it will never work because the scope item will be of the item that you delete and you dont' have access to the entire list.
Here is the demo:
http://plnkr.co/edit/Nr4cJ2m3JqI8NyLFnQhC?p=preview
so, in the meantime i found a solution. Maybe not the best, but it works.
i could attach the needed parameters to a directive attribute and set a $watch on this attribute to stay synchronised with any changes caused by deleting/filtering etc.
for example:
<div item-Index="[$parent.$index,$index,-1,$first, $last]"</div>
and the directive hast the following function in the link function:
scope.$watch(attrs.itemIndex, function(value) {
scope.itemIndex = value;
});
now every item has a unique index defined.
The multilevel keyboard navigation also works now, but this is beyond the scope of this question.

unshifting to ng-repeat array not working while using orderBy

Here is my problem. I've got a comment roll thats using ng-repeat to display the content of a comment array. When the user submits a comment I wan't to unshift to that array in order to display the most recent comment at the top of the list. This works perfectly, but when I add the orderBy filter to the repeat the new comment is applied to the bottom of the repeat.
Here is the comment array HTML:
<ul ng-repeat="comment in comments | filter:{page_id:raceID} | orderBy:'id':'reverse' track by comment.id ">
<li>{{comment.id}}</li>
<li>{{comment.page_id}}</li>
<li>{{comment.user_name}}</li>
<li>{{comment.comment_copy}}</li>
</ul>
Here is the corresponding Controller:
$scope.comment = new newComments({page_id:3, comment_copy:'test comment copy'});
$scope.comment.$save(function(data) {
$scope.comments.unshift($scope.comment);
console.log($scope.comment.id);
});
.....
I scrapped the
orderBy:'id':'reverse'
and instead used a custom filer left on another post here, Angular ng-repeat in reverse. Here is the custom function
app.filter('reverse', function() {
return function(items) {
return items.slice().reverse();
};
});
Now the most recent comment was still not showing up at the top of the page so I had to change from unshift to push. This worked perfectly. Here's the code:
$scope.comment.$save(function(data) {
$scope.comments.push($scope.comment);
});
A few things are wrong in your original post:
orderBy:'id':'reverse' should be orderBy:'id':reverse, where reverse is a boolean (so either replace it by a variable available on the scope, or by a hard-coded true/false). Currently, it defaults to undefined and is interpreted as false.
In your controller code, the field comment.id is not assigned. It will default to undefined and that's the reason sorting does not work as expected.
Additionally:
Array unshift or push will not make a difference in your use case if you correct the aforementioned and the orderBy function is invoked.
In my experience in Angular 1.5.8, track by $index in fact prevented the orderBy function from working. Replacing it by a unique identifier on the object to be repeated, i.e. track by comment.id is preferred. In terms of performance, they should be analogous. Not using the track by clause is the slowest option though. (https://docs.angularjs.org/api/ng/directive/ngRepeat)
that worked for me, would love to know how it affects performance though:
change:
track by comment.id
to:
track by $index
both unshift() and push() should work now, but again, not sure about how much it slows down the DOM regeneration.

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