angular function in ng-show with a promise getting error - angularjs

I'm using restangular for my app. Say I have this in my view:
<div ng-repeat="resource in resources">
<i ng-show="isCheckedLabel(resource)" class="fa fa-checked"> Checked </i>
</div>
and in my controller I have my api call to the service
$scope.getResources = function(){
$scope.getResourcePromise = ResourceService.getResourceById($stateParams.id).then(function(response){
$scope.resources = response;
});
};
and here is my check function which will return true or false
$scope.isCheckedLabel = function(resource){
$scope.getResourcePromise.then(function(){
for(var group in resource.groups){
for(i = 0; i < resource.groups.length; i++){
if (resource.groups[group].isChecked === true){
return true;
} else {
return false;
}
}
}
});
};
What i'm trying to do: loop through each group and if 1 or more is 'checked' I want my label in the front end to show checked.
My function is returning true when one of them is checked and false when none are checked but it's not displaying the i element in the view because I'm getting this error in the console:
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations: []
and it just keeps firing over and over. What's going on here?

First and foremost thing is a value returned inside async then callback will not be the return value of isCheckedLabel.
Looking at your code, you do not need $scope.getResourcePromise.then in isCheckedLabel as the you already have the resource object passed as parameter. Also for loop code seems to wrong. Change it to
$scope.isCheckedLabel = function(resource){
for(var group in resource.groups){
if (group.isChecked === true){
return true;
} else {
return false;
}
}
};

This error occurs only when you trying to update the dom when the $digest is running. In your case you can try to set the checked variable as another property in the $scope variable resources.
$scope.getResources = function () {
$scope.getResourcePromise = ResourceService.getResourceById($stateParams.id).then(function (response) {
$scope.resources = response;
$scope.resources.forEach(function (resource) {
$scope.getResourcePromise.then(function () {
for (var group in resource.groups) {
for (i = 0; i < resource.groups.length; i++) {
if (resource.groups[group].isChecked === true) {
//Here setting another property on the resource object
resource.checked = true;
} else {
resource.checked = false;
}
}
}
});
});
});
};
Now in HTML you can do something like this simply:
<div ng-repeat="resource in resources">
<i ng-show="resource.checked" class="fa fa-checked"> Checked </i>
</div>

Related

$rootScope:infdig error in angularjs 1.4.0

I have the following markup in a form mixed with some asp.net razor:
<div class="account-form__field-container" ng-show="postcodeRequired()" ng-cloak>
#Html.LabelFor(x => x.Postcode)
#Html.TextBoxFor(x => x.Postcode, new { #class = "account-form__field", placeholder = "Postcode here...", ng_required = "postcodeRequired()",ng_validpostcode="", ng_model = "postcode", ng_init = "postcode = '" + Model.Postcode + "'" })
#Html.ValidationMessageFor(x => x.Postcode, null, new { #class = "account-form__error-message" })
<span class="account-form__error-message" ng-show="registrationForm.$submitted && registrationForm.Postcode.$error.required" ng-cloak>
Please enter your postcode
</span>
<span class="account-form__error-message" ng-show="registrationForm.$submitted && !validPostCode()" ng-cloak>
Please enter valid postcode
</span>
</div>
I have a dropdown which will show hide the postcode field, so if uk selected the postcode field will show. The field is required but additionally I am doing a check in whether is a valid postcode via a webservice. The angular controller that deals with form submission looks like:
$scope.submitForm = function () {
$scope.registrationForm.$submitted = true;
if ($scope.enableSubmit()) {
registrationForm.submit();
}
};
$scope.postcodeRequired = function () {
return $scope.country === 'United Kingdom';
};
$scope.validPostCode = function () {
if ($scope.postcodeRequired()) {
if ($scope.postcode !== undefined && $scope.postcode.length > 5) {
postcodeService.ValidatePostCode($scope.postcode).success(function (response) {
return response;
});
} else {
return false;
}
}
return true;
};
$scope.enableSubmit = function () {
return $scope.registrationForm.$valid
&& $scope.passwordsMatch()
&& $scope.acceptTerms
&& $scope.validPostCode();
};
The postCodeService is just doing an http get to validate the post code that returns true or false. The issue i have is on submitting it validates the postcode but then goes into a loop and gives the following error:
angular.min.js:34 Uncaught Error: [$rootScope:infdig] http://errors.angularjs.org/1.4.0/$rootScope/infdig?p0=10&p1=%5B%5D
at angular.min.js:34
at m.$digest (angular.min.js:563)
at m.$apply (angular.min.js:571)
at l (angular.min.js:373)
at O (angular.min.js:388)
at XMLHttpRequest.N.onload (angular.min.js:392)
I have seen other people with this issue when doing an ng-repeat but as you can see I am not doing that.
Any ideas?
Without a plunkr to test against and verify its hard to tell exactly what is causing the infinite digest cycle loop. However I believe it might be cause by the amount of calls made towards your $scope.validPostCode function (which wasn't correctly returning its validity). Basically the change proposed is to only call the validate function when the postcode has been changed (trigged by ng-change on the field). The result of that function sets $scope.validPostCode variable to true or false, which is then what is checked for validity;
HTML (add ng-change to the input)
#Html.TextBoxFor(x => x.Postcode, new { <!-- other attributes -->, ng_change = "validatePostCode()" })
JavaScript
$scope.postcodeRequired = function () {
return $scope.country === 'United Kingdom';
};
// by default its not valid
$scope.validPostCode = false;
// our Validition check
$scope.validatePostCode = function () {
if ($scope.postcodeRequired()) {
if ($scope.postcode !== undefined && $scope.postcode.length > 5) {
postcodeService.ValidatePostCode($scope.postcode).success(function (response) {
$scope.validPostCode = response;
});
} else {
$scope.validPostCode = false;
}
} else {
$scope.validPostCode = true;
}
};
// call our function to properly set the initial validity state.
$scope.validatePostCode();
$scope.enableSubmit = function () {
return $scope.registrationForm.$valid
&& $scope.passwordsMatch()
&& $scope.acceptTerms
&& $scope.validPostCode;
};

How to fill "ion-checkbox ng-repeat" at the time of page load

What I need to do?
I am trying to load the "ion-checkbox ng-repeat" "onDeviceReady" automatically at the time of page load. Below is the HTML code.
<ion-checkbox ng-repeat="item in devList"
ng-model="item.checked"
ng-checked="item.checked">
{{ item.text }}
</ion-checkbox>
But the "ion-checkbox ng-repeat" is getting loaded only when click event is triggered.
The below is the angular-js code which needs to be triggered automaticlly at the time of page load.
Problem: The data for "ion-checkbox ng-repeat" is not getting filled at the time of page load.
Can anyone help to solve the issue.
angular.module('app', ['ionic'])
.controller('AppCtrl', function($scope) {
$scope.devList = [];
window.addEventListener("deviceready", onDeviceReady, true);
function onDeviceReady() {
var options = new ContactFindOptions();
options.filter = ""; // empty search string returns all contacts
options.multiple = true; // return multiple results
filter = ["*"]; // return contact.displayName field
//document.getElementById("lan").innerHTML = lan;
// find contacts
navigator.contacts.find(filter, onSuccess, onError, options);
}
function onSuccess(contacts) {
for (var i = 0; i < contacts.length; i++) {
$scope.devList[i] = {text:""+contacts[i].name.formatted, emails:{email:""+contacts[i].emails[0].value,checked:false}, phno:{phone:""+contacts[i].phoneNumbers[0].value,checked:false},addres:{address: contacts[i].addresses||[],checked:false},checked: false};
$scope.emails[i] = {email:""+contacts[i].emails[0].value+""};
}
}
function onError(contactError) {
alert('onError!');
}
}
You need to call $apply if you do anything outside of angular context
function onSuccess(contacts) {
for (var i = 0; i < contacts.length; i++) {
$scope.devList[i] = {text:""+contacts[i].name.formatted, emails:{email:""+contacts[i].emails[0].value,checked:false}, phno:{phone:""+contacts[i].phoneNumbers[0].value,checked:false},addres:{address: contacts[i].addresses||[],checked:false},checked: false};
$scope.emails[i] = {email:""+contacts[i].emails[0].value+""};
$scope.$apply();
}
}

AngularJS, Add Rows

Morning,
We are trying to implement this add row Plunkr, it seems to work however our input data seems to repeat. Does anyone know of a solution to add a unique id to preview duplicated fields ?
Here is our current Plunkr and LIVE example.
$scope.addRow = function(){
var row = {};
$scope.productdata.push(row);
};
$scope.removeRow = function(index){
$scope.productdata.splice(index, 1);
};
$scope.formData you have is not an array, but just one object. All your rows are bound to that object and hence all of them reference the same data.
The reason you get a new row added is because your ng-repeat is bound to $scope.productData and you add extra record in it. You should bind your form elements to the properties in the row object that you create
a simple example is :
In your template
<div ng-repeat="product in products">
<input type="text" ng-model="product.title">
</div>
In your controller
$scope.addProduct = function(){
var product = {};
$scope.productData.add(product);
}
You'd then always only work with the productData array and bind your model to them.
Even in your backend calls, you'd use productData instead of your formData.
Hope this helps.
U can use a filter : This will return Unique rows only
app.filter('unique', function () {
return function (items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {}, newItems = [];
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function (item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
items = newItems;
}
return items;
};
});
I think the reason why this is happening is that the addRow() function is just pushing an empty son object into the $scope.productdata array, whereas all input fields are bound to $scope.formData[product.WarrantyTestDescription]. I think you mean to bind the input fields to the properties of the product object.

Angular group by and filter

I am trying to group my collection based on a property and at the same time i would like to filter my collection with one property.
When i try the following,
<div data-ng-repeat="(group,parameter) in parameters | filter : { 'type' : '!GroupType' }| groupBy :'group'">
<fieldset>
<legend>{{group}}</legend>
<div data-ng-repeat="par in parameter">
<myfield ng-model="par.value" parameter="par" entry-map="entryMap"></myfield>
</div>
</fieldset>
</div>
I get a lot of errors on the console as follows,
Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!
Watchers fired in the last 5 iterations:
I dont have any watch on my collection. What is the correct way to do this.
I would advise you against using that implementation of the 'groupBy' $filter, use this one instead:
angular.module("sbrpr.filters", [])
.filter('groupBy', function () {
var results={};
return function (data, key) {
if (!(data && key)) return;
var result;
if(!this.$id){
result={};
}else{
var scopeId = this.$id;
if(!results[scopeId]){
results[scopeId]={};
this.$on("$destroy", function() {
delete results[scopeId];
});
}
result = results[scopeId];
}
for(var groupKey in result)
result[groupKey].splice(0,result[groupKey].length);
for (var i=0; i<data.length; i++) {
if (!result[data[i][key]])
result[data[i][key]]=[];
result[data[i][key]].push(data[i]);
}
var keys = Object.keys(result);
for(var k=0; k<keys.length; k++){
if(result[keys[k]].length===0)
delete result[keys[k]];
}
return result;
};
});
Also, have a look at this post, where I discuss why the implementation that you are using is actually pretty bad.
And finally, please support my answer here. Thanks!

Angular filter returning an array of objects causing infinite $digest loop

I have a custom filter which returns an array of matches to search field input and it works, but only after causing an infinite $digest loop. This also apparently only began happening after upgrading from Angular 1.0.6. This is the filter code:
angular.module("Directory.searches.filters", [])
.filter('highlightMatches', function() {
var ary = [];
return function (obj, matcher) {
if (matcher && matcher.length) {
var regex = new RegExp("(\\w*" + matcher + "\\w*)", 'ig');
ary.length = 0;
angular.forEach(obj, function (object) {
if (object.text.match(regex)) {
ary.push(angular.copy(object));
ary[ary.length-1].text = object.text.replace(regex, "<em>$1</em>");
}
});
return ary;
} else {
return obj;
}
}
});
I've seen elsewhere that this could be caused by having the filter inside of an ng-show, or that it's because the array being returned is interpreted as a new array every time it's checked, but I'm not sure how I could fix either problem. You can see a production example of this issue at https://www.popuparchive.com/collections/514/items/4859 and the open source project is available at https://github.com/PRX/pop-up-archive. Thank you!
This is happening because of angular.copy(object). Each time the digest cycle runs, the filter returns an array of new objects that angular has never seen before, so the the digest loop goes on forever.
One solution is return an array containing the original items that match the filter, with a highlightedText property added to each item...
angular.module("Directory.searches.filters", [])
.filter('highlightMatches', function() {
return function (items, matcher) {
if (matcher && matcher.length) {
var filteredItems = [];
var regex = new RegExp("(\\w*" + matcher + "\\w*)", 'ig');
angular.forEach(items, function (item) {
if (item.text.match(regex)) {
item.highlightedText = item.text.replace(regex, "<em>$1</em>");
filteredItems.push(item);
}
});
return filteredItems;
} else {
angular.forEach(items, function (item) {
item.highlightedText = item.text;
});
return items;
}
}
});
You can bind to the highlightedText property, something like...
<div>
Results
<ul>
<li ng-repeat="item in items | highlightMatches : matcher" ng-bind-html="item.highlightedText"></li>
</ul>
</div>

Resources