FIDDLE
How can i count the number of errors made on form ?
HTML
<div ng-show="form.$submitted && form.$invalid">
Sorry but 3 errors have been made.
</div>
One way you could do this by using the specific count of a specific error criteria, required, pattern etc.. that are available as a part of form's $error[prop] array. In your case you could try using form.$error.required.length:-
<div ng-show="form.$submitted && form.$invalid">
Sorry but {{form.$error.required.length}} errors have been made.
</div>
Demo
You could add a function on the controller which can determine the number of errors on the form and return it, and use it in the view to display the total error count.
$scope.numberoferrors=function(form){
var count = 0,
errors = form.$error;
angular.forEach(errors, function(val){ if(angular.isArray(val)) { count += val.length; } });
//Object.keys(errors).forEach(function(key){ count += errors[key].length }); //get count of all error categories from form
return count;
};
Demo
Related
I am working on a cart using Angular, during process of that I faced a issue about calculation.
In my scenario I have code something like
<label>No. of item</label>
<div>{{totalItems}}</div>
<div ng-repeat="cartElement in currentCartDetails.cartElements">
<span>{{cartElement.productName}}</span>
<span>{{cartElement.productCode}}</span>
<span>{{cartElement.productPrice}}</span>
<span>{{cartElement.quantity}}</span>
</div>
What I want, add all something like
totalItems += cartElement.quantity
I know there are so many option to display the value
eg. using calculation from server side, iteration in calculation to controller
But what I am looking for, when I am iterate object on view page is there any way to calculate there and get benefit of tow way binding.
Have you tried to do it with function?
Define a function inside your controller like calculateTotal then call it for every iteration.
$scope.totalItems = 0;
...
$scope.calculateTotal = function(cart){
$scope.totalItems += cart.quantity;
}
...
then in your template
<div ng-repeat="cartElement in currentCartDetails.cartElements" ng-init="calculateTotal(cartElement)">
<span>{{cartElement.productName}}</span>
<span>{{cartElement.productCode}}</span>
<span>{{cartElement.productPrice}}</span>
<span>{{cartElement.quantity}}</span>
</div>
You can also achieve this by ,
Method 1:
HTML :
<div>{{gettotalItems(currentCartDetails.cartElements)}}</div>
JS:
$scope.gettotalItems = function(cart_data){
var num = 0;
for(var i=0;i<cart_data.length;i++) {
num += cart_data[0].quantity;
}
return num;
}
Method 2:
HTML:
<div ng-init="gettotalItems(currentCartDetails.cartElements)>{{totalItems}}</div>
JS:
$scope.gettotalItems = function(cart_data){
var num = 0;
for(var i=0;i<cart_data.length;i++) {
num += cart_data[0].quantity;
}
$scope.totalItems = num;
}
Advantage is You can get the total cart value directly in one call by passing entire List of Objects. instead of, counting each time during iteration.
I need to create groups of things that come as a flat array, so I can open and close grid rows using a CSS grid system.
Here's how my HTML looks:
<div ng-repeat="row in items | GroupsOf: 3" class="row">
[show stuff]
</div>
And here's the filter I wrote to support this:
.filter('GroupsOf', function(){
//Takes an array of things and groups them into
// sub-arrays with numPerGroup things in each
return function(things, numPerGroup){
var i, group = -1, groups = []
for(i = 0; i < things.length; i++){
//if it's a new group:
if(i % numPerGroup === 0){
groups.push([])
group++
}
groups[group].push(things[i])
}
return groups//
}
})
Although things render as expected, I'm getting infinite digest error when this runs, and therefore not everything gets wired up properly.
Why do I get that error, and how do I fix the filter to work w/o erring?
I'd really prefer to do this as a filter rather than grouping the data in the controller, but I'll go the later route if someone can explain to me why it's just not achievable as a filter.
If you wanted to use $filter in the controller( which is certainly more performant and fixes the infinite $digest loop issue) you can do :
angular.module('myApp', [])
.controller('myCtrl', function($scope, $filter) {
$scope.items = [
'one','two','three',
'unos','dos','tres',
'uno','due','tre'
]
$scope.grouped = $filter('GroupsOf')($scope.items , 3)
console.log($scope.grouped)
})
.filter('GroupsOf', function(){
//Takes an array of things and groups them into
// sub-arrays with numPerGroup things in each
return function(things ,numPerGroup){
var i, group = -1, res = []
for(i = 0; i < things.length ; i++){
//if it's a new group:
if(i % numPerGroup === 0){
res.push([])
group++
}
res[group].push(things[i])
}
return res
}
})
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="row in grouped" class="row">
{{row}}
</div>
</div>
looking at the infdig documentation it seems your problem is caused by
.. binding to a function which generates a new array every time it is called.
that should be the reason $digest never finds a clean state.
i am about to bang my head to walls. i thought i had an understanding of how angular works (filters too). but i just cant find the problem about my filter. it causes infdig. and i even dont change source array in filter.
(function () {
angular.module('project.filters').filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var resultArray = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
resultArray.push(data.slice(i, i + chunk));
}
return resultArray;
};
});
})();
i have lists where i need to split data to x columns. it is complicated to solve with limitTo.
(limitTo: $index*x | limitTo: $last ? -z : -x)
it causes a dirty template file. so i decided to create a filter which splits an array to groups.
[1,2,3,4,5,6,7,8] -> [[1,2,3],[4,5,6],[7,8]]
so i can easily use it in my template.
Can u help me about what causes infdig in this filter?
Edit: the error message itself looks strange with some numbers in that don't appear anywhere in the code, which can be seen at http://plnkr.co/edit/pV1gkp0o5KeimwPlEMlF
10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: [[{"msg":"fn: regularInterceptedExpression","newVal":23,"oldVal":20}],[{"msg":"fn: regularInterceptedExpression","newVal":26,"oldVal":23}],[{"msg":"fn: regularInterceptedExpression","newVal":29,"oldVal":26}],[{"msg":"fn: regularInterceptedExpression","newVal":32,"oldVal":29}],[{"msg":"fn: regularInterceptedExpression","newVal":35,"oldVal":32}]]
HTML Template
<div class="row" ng-repeat="chunk in docProfile.SysMedicalInterests | splitListFilter: 3">
<div class="col-md-4" ng-repeat="medInterest in chunk">
<label style="font-weight:normal;">
<input type="checkbox" value="{{medInterest.ID}}" ng-click="docProfile.saveInterest(medInterest.ID)" ng-checked="docProfile.isMedChecked(medInterest.ID)"> {{medInterest.Name}}
</label>
</div>
</div>
Controller Code
var me = this;
me['SysMedicalInterests'] = null;
var loadMedicalInterests = function(){
var postData = { 'Data': me['data']['subData'] };
return docService.loadMedicalInterests(postData).then(function(resp) {
me['SysMedicalInterests'] = resp['data'];
}, function(){});
};
loadMedicalInterests();
so array starts with a null reference and loads data from server. which changes array causes a second filter run. but it doesnt stop after that
Edit: here is plunkr http://plnkr.co/edit/OmHQ62VgiCXeVzKa5qjz?p=preview
Edit: related answer on so https://stackoverflow.com/a/21653981/1666060 but this still doesn't explain angular built in filters.
here is angularjs limitTo filter source code
https://github.com/angular/angular.js/blob/master/src/ng/filter/limitTo.js#L3
About what exactly causes it, I suspect is something to do with the fact that every time you run the filter a new array reference is created and returned. However, Angular's built-in filter filter does the same thing, so I'm not sure what is going wrong. It could be something to do with the fact that it's an array of arrays that is being returned.
The best I have come up with is a workaround/hack, to cache the array reference manually as an added property, which I've called $$splitListFilter on the array, and only change it if it fails a test on angular.equals with the correct results calculated in the filter:
app.filter('splitListFilter', function () {
return function (data, chunk) {
if(!data || data.length === 0){
return data;
}
var results = [];
for (var i = 0, j = data.length; i < j; i += chunk) {
results.push(data.slice(i, i + chunk));
}
if (!data.$$splitListFilter || !angular.equals(data.$$splitListFilter, results)) {
data.$$splitListFilter = results;
}
return data.$$splitListFilter;
};
});
You can see this working at http://plnkr.co/edit/vvVJcyDxsp8uoFOinX3V
The answer uses Angular 1.3.15
The JS fiddle works fine: http://jsfiddle.net/3tzapfhh/1/
Maybe you use the filter wrongly.
<body ng-app='app'>
<div ng-controller='ctrl'>
{{arr | splitListFilter:3}}
</div>
</body>
I'm trying to achieve customized numbering while listing all items in an array.
All items in array are rendered using ng-repeat & ui.sortable.
Numbering must be done in such a way that, for an array item "statement", count should not be increased & displayed.
(Else I may be used $index instead of an external count.)
For any other array item, count should be increased & displayed.
The solution that got me the the closest result was the one where I passed $index into a filter function written in the controller.
like in HTML:
<li ng-repeat="question in questions">
<div class="no"> {{ filterIndex(question.question, $index) }} </div>
<div>{{question.question}}</div>
</li>
in controller:
var filterValue = 0;
$scope.filterIndex = function (value, count) {
if (count === 0) {
filterValue = 0;
}
if (value !== 'statementText') {
filterValue = filterValue + 1;
return filterValue;
}
else {
return '"';
}
};
Even it was working without any errors, the count returned from function is not get updated like we get with $index when we update the order using ui-sortable.
see it here: js-fiddle using filter function
means, once it rendered (4) in <[ (4) fourth question ]> will remain same even if we moved it to top or bottom by dragging.
I tried different ways and almost everything ended up on 'Maximum iteration limit exceeded.'.
Real scenario is a little bit complex as it contains nested ng-repeats and similar counting with digits and numbers alternatively in child loops.
Here is link to start fresh:
js-fiddle
You can inject this to your controller, it will listen for array changes, and update the indexes:
var update = function(){
var currentCount = 0;
var questions = $scope.questions;
for(var j = 0; j < questions.length; j++){
if(questions[j].question != null){
if(questions[j].question.indexOf("statement") < 0){
questions[j].calculatedIndex = ++currentCount;
}
}
}
};
$scope.$watchCollection('questions', function(){
update();
});
update();
probably needs a bit fine-tuning as I didn't concentrate on your question completely. In the ng-repeat, you now have access to calculatedIndex. It will be "NULL" for statement items, you can use ng-show for that.
just try to add extra option:
ng-init='question.index = $index+1'
http://jsfiddle.net/ny279bry/
I want to check that an input value is valid, (i.e. if someone is spending at least 25 cents on a candy from a list of candies), and if not, tag that input as $invalid, so the form doesn't get submitted.
<form novalidate name="candyForm">
<div ng-repeat="candy in candies">
<input name="candyCost" type="text" ng-model="candy.cost" required>
<div class="error" ng-show='checkEnoughMoney()'>You only have 1 dollar to spend</div>
<div class="error" id="candyError{{$index}}">You must spend at least 25c per candy</div>
</div>
</form>
The checkEnoughMoney() is:
$scope.checkEnoughMoney = function() {
var total = 0;
for(var i = 0; i < $scope.candies.length; i++) {
var amount = parseFloat($scope.sweets[i].cost) || 0;
total = total + amount;
if((parseFloat(amount) >= 25) && (parseFloat(amount) <= 100)) {
$("#candyError" + i).hide();
}
else {
$("#candyError" + i).show();
}
}
if(total > 100) {
$scope.candyForm.candyCost.$setValidity('candyForm.candyCost',false);
return true;
}
else {
$scope.candyForm.candyCost.$setValidity('candyForm.candyCost',true);
return false;
}
};
Setting the $scope.candyForm.candyCost to true or false works here as it affects all the instances if candies, which is great, as it needs to. But how do I make the single instance invalid if the person has entered less than 25 cents or more than 100 cents?
As you can see, I have cheated to make the 25>=cost>=100 messages show when they should, but the form is still submitting, as the input is not being set to invalid. If I try $scope.candies[index].setValidity("cost", false), it throws an error, as the parent scope can't access the scope inside the ng-repeat. Is there any way to get around this?
Here's an example of handling forms in Angular that should take care of your problem.
http://docs.angularjs.org/cookbook/advancedform
I prefer to setup a controller fn to handle requests and pass those through a service.