AngularJS - how to highlight/add class to a max value - angularjs

Let say I have a very simple data that Im looping through with ng-repeat.
Now, how can I highlight (add a css class) to a highest value in the data.
Data:
$scope.marks = [
{point:'11'},
{point:'2'},
{point:'23'}
];
html:
<ul>
<li ng-repeat="value in marks" class={{here add class for value 23}}> //how to add class to li with max value, in this case it is 23
{{ value.point}}
</li>
</ul>
Many thanks for your help
PLUNKR

You can use calculate the max value in a watch:
$scope.$watchCollection('marks', function(items) {
$scope.maxPoint = -1; // assuming we won't have negative values
angular.forEach(items, function(item) {
if (parseInt(item.point) > $scope.maxPoint) {
$scope.maxPoint = item.point;
}
});
});
Then use ngClass directive in your markup:
<li ng-repeat="value in marks" ng-class="{max : value.point == maxPoint}">
{{ value.point}}
</li>
Like Petr pointed out this won't perform well. Calculating the max value on changes is a better way. Here's a working plunk : http://plnkr.co/edit/F8yjoWR0ZWVF4tcck1rH?p=preview

Related

Angular Add class to ng-repeat element that is updated or added only

Hi I am trying to dynamically add a class to an element that is updated on the view.
http://jsfiddle.net/9s1rfwa8/7/
<div ng-app>
<div ng-controller="TodoCtrl">
<ul>
<li ng-repeat="todo in todos" class="bigfont todo done-{{todo.done}}"> <span>{{todo.text}}</span>
</li>
</ul>
</div>
function TodoCtrl($scope) {
$scope.todos = [{
text: 'learn angular',
done: true
}, {
text: 'build a demo angular appsdfsdfsf',
done: false
}, {
text: 'build an awesome angular app',
done: false
}];
}
How do I add a class to the ng-repeat element that is only changed and not to others.
I am trying to give it a flip effect to the elements that changes.
Ng-repeat will duplicate element that is repeated. If you want to apply class to only one of the element repeated, then you need to add a condition e.g. $index === 0 to ng-class.
ng-class={'first-item-class': $index === 0}
this will apply to the first element only. Use any kind of condition logic to identify its the one you want to apply to.
You can use a custom filter for this, custom filter will be executed whenever there is a change.
For example you can change done property inside your todo object every time user clicks on the row and use the value of todo.done to set class inside your filter
html
<li ng-repeat="todo in todos | filter:handleChange"
class="bigfont todo done-{{todo.done}} {{todo.class}}"
ng-click="todo.done = !todo.done">
<span>{{todo.text}}</span>{{todo.class}}
</li>
js
$scope.handleChange = function(row){
if(!row.done){
row.class = '';
}
else{
row.class = 'changed'
}
return true;
}
this should give you a general idea on how to approach your problem, you can find a working sample in the following plunker.
Demo

ng-class not working with ng-repeat

I have a situation where I have to add class according to the condition and the ng-class is working according to it even the condition in the ng-class is true.
<ul id="" class="clowd_wall" dnd-list="vm.cardData[columns.id].data"
dnd-drop="vm.callback(item,{targetList: vm.cardData[columns.id].data, targetIndex: index, event: event,item:item,type:'folder',eventType:'sort','root':'folder',current_parent:'folder'})" ng-model="vm.cardData[columns.id].data">
<div class="emptyCol" ng-if="vm.cardData[columns.id].data.length==0">Empty</div>
<li class="dndPlaceholder"></li>
<li class="cont____item" ng-repeat="card in vm.cardData[columns.id].data | orderBy:vm.sort" dnd-draggable="card"
dnd-effect-allowed="move"
dnd-allowed-types="card.allowType"
dnd-moved="vm.cardData[columns.id].data.splice($index, 1)"
dnd-selected="vm.tree.selected = card" ng-class="{emptyCard:card.data.length==0,zoomin:vm.zoomin=='zoomin',emptyCard:!card.data}">
<div class="item" style="height:79%">
<ng-include ng-init = "root = columns.id" src="'app/partials/card.html'"></ng-include>
</div>
</li>
</ul>
ng-class="{'emptyCard': (!card.data || !vm.cardData[columns.id].data.length),'zoomin':(vm.zoomin=='zoomin')}">
Seems like you want to use vm.cardData[columns.id].data.length instead of card.data.length
Your question is not clear as don't know what card.data will contain and ".data" will be present for each iteration
If it is array then this will work "card.data.length" and if there is no "data" key in "card" then ".length" will through error i.e. if card.data itself undefined then it will not have "length" property.
Try to add condition in ng-class one by one then you will be able to figure out which condition is causing problem.
Made some small change
ng-class="{emptyCard: card.data.length==0 || !card.data,zoomin: vm.zoomin=='zoomin'}"
If you have multiple expression, try old fashioned, if it looks best:
Controller:
$scope.getcardClass = function (objCard, strZoomin) {
if (!card.data) {
return 'emptyCard';
} else if (strZoomin =='zoomin') {
return 'zoomin';
} else if (card.data.length == 0) {
return 'emptyCard';
}
};
HTML:
ng-class="vm.getcardClass(card, vm.zoomin)"
NOTE: Replace vm with your controller object.

How to set a default value for AngularJS ng-repeat?

<li ng-repeat="item in quantityList track by $index" ng-model="isPageValid='true'">
<input value="item.quantity" ng-blur="validateQuantity(item)" ng-model="item.quantity">
</li>
I'm trying to set a default value for a status variable each time an ng-repeat loop occurs. I've tried ng-model, but that doesn't work. For instance,
I'd like to set isPageValid="true" before each time the ng-repeat loop runs. 'True' is will be the default value, and the validation function will test whether isPageValid should be set to 'false'.
I'd like the ng-repeat loop to run each time the ng-blur is exercised.
NOTE: I understand the way I'm using ng-model is incorrect, but this is just to illustrate the issue.
HTML:
<li ng-repeat="item in quantityList track by $index" ng-model="isPageValid='true'">
<input value="item.quantity" ng-blur="validateQuantity(item)" ng-model="item.quantity">
</li>
JS:
scope.validateQuantity = function(item){
var qty = item.quantity;
if(parseInt(qty) >=1 && parseInt(qty) <= 200){
item.isQuantityValid = true;
}else{
item.isQuantityValid = false;
scope.isPageValid = false;
}
}
The loop creates a list of input boxes. The objective is to create a global validation value called isPageValid which is 'false' if the validation by the JS fails for any input box. Note, when ng-blur is exercised, the JS validation runs and loop should re-run.
I believe ng-init could help...
<ul ng-init="isPageValid='true'">
<li ng-repeat="item in quantityList track by $index" >
<input ng-blur="validateQuantity(item)" ng-model="item.quantity">
</li>
</ul>
Note that it would be better practice to initialise isPageValid in the controller that is in the same scope as your validateQuantity function.
Here is an example of how you could initialise isPageValid in your controller and update the value after each call to validateQuantity...
In your controller:
scope.isPageValid = true;
function updatePageValid() {
scope.isPageValid = scope.quantityList.every(item => item.isQuantityValid);
}
scope.validateQuantity = function(item) {
var qty = parseInt(item.quantity);
item.isQuantityValid = (qty >= 1 && qty <= 200);
updatePageValid();
});

How to search on the displayed data and not the backing data using Angular

I am using ng-repeat to create a table of data:
<div class="divTable" ng-repeat="expense in exp.expenses | filter:exp.query">
<div>{{expense.amount | ldCurrency : true}}</div>
...
</div>
A couple of the cells that I am creating are being modified through an Angular filter. In the example above, I am changing the integer to a currency. So the original 4 is changed to $4.00. When I filter the entire list with my exp.query, it does not modify the exp.query search term through the ldCurrency.
The means that if I search on $4, it will not find it, because the backing data is 4, even though $4 is on the page.
I know this is confusing, with the two types of filters that I am talking about here.
How can I search on the data that is being shown on the page and not on the backing data?
You have to create you own filter. What you want to do to is a bad idea, because you are melding the view layer and the model layer.
A example of a filter.
The html:
<input ng-model="query" ng-trim="true">
<span>Finding: </span><span>{{ query }}</span>
<div ng-repeat="product in products | productsFilter: query">
<strong>{{ $index }}</strong>
<span>{{ product.name }}</span>
<span>{{ product.price | currency: '$'}}</span>
</div>
The custom filter:
.filter('productsFilter', [function () {
// Private function: It removes the dollar sign.
var clearQuery = function (dirtyQuery) {
var index = dirtyQuery.indexOf('$');
if (index === -1)
return dirtyQuery;
return dirtyQuery.substr(index+1, dirtyQuery.length-index)
};
// The Custom filter
return function (products, query) {
if (query === '') return products;
var newProducts = [];
angular.forEach(products, function (product) {
var cleanQuery = clearQuery(query);
var strProductPrice = '' + product.price;
var index = strProductPrice.indexOf(cleanQuery);
if (index !== -1) {
newProducts.push(product);
}
});
return newProducts;
};
}]);
The key is in the angular.forEach. There I decide if the product will belong to the new filtered collection. Here you can do the match you want.
You can find the complete example in full plucker example and see a lot of filters in the a8m's angular-filter

Custom sort function in ng-repeat

I have a set of tiles that display a certain number depending on which option is selected by the user. I would now like to implement a sort by whatever number is shown.
The code below shows how I've implemented it (by gettting/setting a value in the parent cards scope). Now, because the orderBy function takes a string, I tried to set a variable in the card scope called curOptionValue and sort by that, but it doesn't seem to work.
So the question becomes, how to I create a custom sort function?
<div ng-controller="aggViewport" >
<div class="btn-group" >
<button ng-click="setOption(opt.name)" ng-repeat="opt in optList" class="btn active">{{opt.name}}</button>
</div>
<div id="container" iso-grid width="500px" height="500px">
<div ng-repeat="card in cards" class="item {{card.class}}" ng-controller="aggCardController">
<table width="100%">
<tr>
<td align="center">
<h4>{{card.name}}</h4>
</td>
</tr>
<tr>
<td align="center"><h2>{{getOption()}}</h2></td>
</tr>
</table>
</div>
</div>
and controller :
module.controller('aggViewport',['$scope','$location',function($scope,$location) {
$scope.cards = [
{name: card1, values: {opt1: 9, opt2: 10}},
{name: card1, values: {opt1: 9, opt2: 10}}
];
$scope.option = "opt1";
$scope.setOption = function(val){
$scope.option = val;
}
}]);
module.controller('aggCardController',['$scope',function($scope){
$scope.getOption = function(){
return $scope.card.values[$scope.option];
}
}]);
Actually the orderBy filter can take as a parameter not only a string but also a function. From the orderBy documentation: https://docs.angularjs.org/api/ng/filter/orderBy):
function: Getter function. The result of this function will be sorted
using the <, =, > operator.
So, you could write your own function. For example, if you would like to compare cards based on a sum of opt1 and opt2 (I'm making this up, the point is that you can have any arbitrary function) you would write in your controller:
$scope.myValueFunction = function(card) {
return card.values.opt1 + card.values.opt2;
};
and then, in your template:
ng-repeat="card in cards | orderBy:myValueFunction"
Here is the working jsFiddle
The other thing worth noting is that orderBy is just one example of AngularJS filters so if you need a very specific ordering behaviour you could write your own filter (although orderBy should be enough for most uses cases).
The accepted solution only works on arrays, but not objects or associative arrays. Unfortunately, since Angular depends on the JavaScript implementation of array enumeration, the order of object properties cannot be consistently controlled. Some browsers may iterate through object properties lexicographically, but this cannot be guaranteed.
e.g. Given the following assignment:
$scope.cards = {
"card2": {
values: {
opt1: 9,
opt2: 12
}
},
"card1": {
values: {
opt1: 9,
opt2: 11
}
}
};
and the directive <ul ng-repeat="(key, card) in cards | orderBy:myValueFunction">, ng-repeat may iterate over "card1" prior to "card2", regardless of sort order.
To workaround this, we can create a custom filter to convert the object to an array, and then apply a custom sort function before returning the collection.
myApp.filter('orderByValue', function () {
// custom value function for sorting
function myValueFunction(card) {
return card.values.opt1 + card.values.opt2;
}
return function (obj) {
var array = [];
Object.keys(obj).forEach(function (key) {
// inject key into each object so we can refer to it from the template
obj[key].name = key;
array.push(obj[key]);
});
// apply a custom sorting function
array.sort(function (a, b) {
return myValueFunction(b) - myValueFunction(a);
});
return array;
};
});
We cannot iterate over (key, value) pairings in conjunction with custom filters (since the keys for arrays are numerical indexes), so the template should be updated to reference the injected key names.
<ul ng-repeat="card in cards | orderByValue">
<li>{{card.name}} {{value(card)}}</li>
</ul>
Here is a working fiddle utilizing a custom filter on an associative array: http://jsfiddle.net/av1mLpqx/1/
Reference: https://github.com/angular/angular.js/issues/1286#issuecomment-22193332
The following link explains filters in Angular extremely well. It shows how it is possible to define custom sort logic within an ng-repeat.
http://toddmotto.com/everything-about-custom-filters-in-angular-js
For sorting object with properties, this is the code I have used:
(Note that this sort is the standard JavaScript sort method and not specific to angular) Column Name is the name of the property on which sorting is to be performed.
self.myArray.sort(function(itemA, itemB) {
if (self.sortOrder === "ASC") {
return itemA[columnName] > itemB[columnName];
} else {
return itemA[columnName] < itemB[columnName];
}
});
To include the direction along with the orderBy function:
ng-repeat="card in cards | orderBy:myOrderbyFunction():defaultSortDirection"
where
defaultSortDirection = 0; // 0 = Ascending, 1 = Descending

Resources