$rootScope:infdig error in angularjs 1.4.0 - angularjs

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;
};

Related

createError not working when typing, even though the service call is made

I have a Yup method that I am calling, this checks whether a 10 digit number has been used before. It returns true/false from the service. If it is in use, it should display a validation error on the input box.
It definitely passes into this Yup method and is handled, returning true or false, however the validation doesn't appear, until the input box is blurred (When blurred it calls this method again, however it hits the earlier createError call where the id's are the same and the lastNumber is false)
At the point where the console.log is, if the value is false I would expect it to display the error via createError but it does not.
let lastNumber:any;
let lastResult:boolean;
Yup.addMethod(Yup.string, "isNumberInUse", function (errorMessage){
return this.test(`test-number-is-inuse`, errorMessage, function (value, ctx) {
const { path } = this;
let digits = value || '';
let patientId = ctx.parent.id;
if(patientId === undefined) {
patientId = '';
}
if(digits.length === 0) {
lastNumber = '';
lastResult = false;
return true;
}
if(digits.length === 10) {
if(lastNumber === ctx.parent.number && !lastResult) {
return this.createError({path, message: errorMessage})
}
if(lastResult){
return true;
}
MyService.IsNumberAvailable(digits, patientId.toString())
.then((response:boolean) => {
lastNumber = digits;
lastResult = response;
console.log(response);
return (response || this.createError({path, message: errorMessage}));
})
.reject(() => {
return this.createError({path, message: "Unable to check Number usage"});
});
}
lastNumber = '';
lastResult = false;
return false;
});
});

Switch between 2 ng-shows

I have two elements with a ng-show in them,
%a.follow{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow
%a.unfollow{"ng-click" => "unfollowUser(user)", "ng-show" => "isFollowed(user.id)"} unfollow
It depends on the user.id which ng-show is being rendered in the template. So only one of the two ng-shows is displayed.
So for example a user wants to start following another user. Then the follow link is displayed.
%a.follow{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow
When a user clicks on it, I would like to hide the clicked ng-show, and show the unfollow ng-show so that the user can unfollow the just followed user.
The follow and unfollow user function,
$scope.followUser = function (user) {
followUser.create({
followed_id: user.id
}).then(init);
Notification.success(user.name + ' is toegevoegd als vriend.');
}
$scope.unfollowUser = function(user){
unfollowUser.unfollowUser(user).then(function(){
},function(){
}).then(init);
Notification.success(user.name + ' is verwijderd als vriend.');
}
And the isFollowed function,
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
console.log ($scope.users)
angular.forEach(response, function(user){
$scope.user = user
$scope.isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i=0; i<following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
})
})
I've tried building this,
<a ng-click="follow=false ;unfollow=true", ng-show="follow">Follow!</a>
<a ng-click="follow=true; unfollow=false", ng-show="unfollow">Unfollow!</a>
This does switch between the two ng-shows, but when I try to get the isFollowed(user.id), !isFollowed(user.id) in them the code crashes.
You should create single function to follow/unfollow, Here in the code snippet I have introduced a new property i.e. isFollowed to object user whose value is set using the isFollowed function.
Additionally, Don't overuse isFollowed(user.id) method, it will be huge performance hit.
HTML
<a ng-click="followUnfollowUser(user)"> {{ user.isFollowed : "Unfollow!" : "Follow!"}} </a>
Script
$scope.followUnfollowUser = function(user) {
//If followed - unfollow
if (user.isFollowed) {
unfollowUser.unfollowUser(user).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
}).then(init);
Notification.success(user.name + ' is verwijderd als vriend.');
} else {
followUser.create({
followed_id: user.id
}).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
}).then(init);
Notification.success(user.name + ' is toegevoegd als vriend.');
}
}
//Define method to check wheather current user is beign followed
var isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i = 0; i < following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
//Fetch Users
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
//Iterate and create isFollowed property
angular.forEach($scope.users, function(user) {
user.isFollowed = isFollowed(user.id);
})
})
Note: I'm not familiar with following syntax thus used standard HTML.
%a.follow{"ng-click" => "followUser(user)", "ng-show" => "!isFollowed(user.id)"} follow
Alrhgout Satpal did point me to the right direction and helped me with some code. His answer isn't complete. So I've decided that add the code I'm using for this function (made with the help of Satpal!).
I've created a followUnfollowUser function. But instead of having two .then(init) I have one init() at the end of the function. Having the two inits gave me some looping trouble.
$scope.followUnfollowUser = function(user) {
//If followed - unfollow
if (user.isFollowed) {
unfollowUser.unfollowUser(user).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
})
Notification.success(user.name + ' is verwijderd als vriend.');
} else {
followUser.create({
followed_id: user.id
}).then(function() {
user.isFollowed=!user.isFollowed
}, function() {
})
Notification.success(user.name + ' is toegevoegd als vriend.');
}
init();
}
Then the init function,
var init = function () {
loadCurrent_user.loadCurrent_user().then(function(response) {
$scope.current_user = response.data;
});
usersService.loadUsers().then(function(response) {
$scope.users = response.data;
//Iterate and create isFollowed property
angular.forEach($scope.users, function(user) {
user.isFollowed = isFollowed(user.id);
})
})
var isFollowed = function(userId) {
var following = $scope.current_user.following;
for (var i = 0; i < following.length; i++) {
if (following[i].id == userId) {
return true;
}
}
return false;
}
}
First I load the current user so that the $scope.current_user gets updated when a user is being followed/unfollowed. And then I iterate through each user and create the isFollowed value using the isFollowed function.
And in my template I have,
%a{"ng-click" => "followUnfollowUser(user)"}
-# {{ user.isFollowed }}
{{ user.isFollowed ? "Unfollow user" : "Follow user"}}

Trying to bind a variable to a row in a Factory array (angular)

I have a factory that is storing an array where objects are pushed in as buttons are pressed. I want to bind a variable on a page to the first object in this array. Here's my factory:
angular.module('myApp')
.factory('errorHandler', function () {
var errorArray = [];
function compareObjs(a,b) {
if(a.type === "Alert" && b.type === "Info") {
return -1;
}
else if (a.type === "Info" && b.type === "Alert") {
return 1;
}
else {
if(a.timestamp > b.timestamp) {
return -1;
}
else if (a.timestamp < b.timestamp) {
return 1;
}
else {
return 0;
}
}
}
errorArray.addError = (function (type, message) {
var timestamp = Date.now();
errorArray.push({type: type, message: message, timestamp: timestamp});
errorArray.sort(compareObjs);
});
errorArray.removeError = (function (index) {
if(errorArray.length >= index) {
errorArray.splice(index, 1);
}
});
return errorArray;
})
And Controller:
.controller('ErrorModalCtrl', ['errorHandler', '$scope', function (errorHandler, $scope) {
$scope.errorModal = {
title: 'Notification Centre',
errorList: []
};
$scope.addError = function(type, message) {
errorHandler.addError(type, message);
console.log(errorHandler.length+"just added");
};
$scope.errorModal.errorList = errorHandler;
$scope.mostRecentError = errorHandler[0];
}]);
So when the page loads the array will be empty, but as I press these buttons $scope.addError is triggered and objects are pushed to the array. I have checked this is working correctly. However, on my HTML (within the scope of the controller) I have
{{mostRecentError.message}}
and this never populates. Why is this the case? Have I misunderstood the dependencies somewhere?
Thanks
The line
$scope.mostRecentError = errorHandler[0];
is executed when your controller is instantiated. At that moment, the error hasn't pressed any button yet. So errorHandler[0] is undefined, and $scope.mostRecentError is thus initialized to undefined.
When the user pressed the button errorHandler will have its first element, but that line of code won't be magically reexecuted, so $scope.mostRecentError wil stay undefined.
Instead of initializing a variable, you should define a function in the controller, that returns the most recent error. That, or $scope.mostRecentError must be initialized inside the addError() function of the controller, to be updated every time an error is added.

Angular typeahead response error

I am implementing an input which allow to select multiple values as tags. I am working with angular-ui-bootstrap-typeahead
The following example with dummy data works fine:
view:
<script type="text/ng-template" id="form_field_ref">
<div class='container-fluid' ng-controller="typeHeadTestCtrl">
<input type="text" ng-model="selected" typeahead="x.formatted_address for x in dynamicSearch($viewValue, field.displayName)" class="form-control" typeahead-on-select='onSelect($item, field)'>
</div>
</script>
part of controller:
$scope.getLocation = function(val) {
return $http.get('http://maps.googleapis.com/maps/api/geocode/json', {
params: {
address: val,
sensor: false
}
}).then(function(response){
console.log(response);
return response.data.results.map(function(item){
console.log(item);
//items attribute=> address_components, formatted_address, place_id....
return item;
});
});
};
But when I try to connect to my actual data I get the following error:
TypeError: Cannot read property 'length' of undefined
at ui-bootstrap-tpls.js:3637
at processQueue (angular.js:13170)
at angular.js:13186
at Scope.$eval (angular.js:14383)
at Scope.$digest (angular.js:14199)
at Scope.$apply (angular.js:14488)
at $$debounceViewValueCommit (angular.js:20944)
at $setViewValue (angular.js:20916)
at HTMLInputElement.listener (angular.js:19632)
at HTMLInputElement.eventHandler (angular.js:3011)
Here is the code that fails:
view:
<input type="text" ng-model="selected" typeahead="x.theVal for x in dynamicSearch($viewValue, field.displayName)" class="form-control" typeahead-on-select='onSelect($item, field)'>
the parts of the controller:
dynamicSearch() prepares what data to request on call of getDbRefDocs():
$scope.dynamicSearch = function(searchTerm, name) {
var allowed = {};
var classString = "";
allowed = datamodel.classes[$routeParams.class].attribute[name].allowed;
for (key in allowed){
classString = classString + key + "|";
}
//remove last pipeline
classString = classString.slice(0, -1);
$scope.getDbRefDocs(searchTerm, name, classString);
};
$scope.getDbRefDocs = function(searchTerm, name, classString) {
var url = '/api/v2/docs/' + classString;
return $http.get(url, {
params: {
'>displayName': searchTerm,
count: 5
}
}).then(function(response){
var data = response.data.data;
console.log('data:'+data);
var requested = [];
angular.forEach(data.requested, function(searchTerm, k, o) {
requested.push(createDBOifNecessary(searchTerm));
});
$scope.item=[];
$scope.options=[];
$scope.options[name] = [];
for (key in requested) {
if (requested.hasOwnProperty(key)) {
//This is the storing value
//console.log(requested[key].cid);
//this is the display value
//console.log(requested[key].attributes.displayName[0]);
$scope.options[name][key] = requested[key].attributes.displayName[0];
$scope.item.push({
'theName':requested[key].attributes.displayName[0],
'theVal':requested[key].cid
});
}
}
console.log('item:'+$scope.item);
return $scope.item;
});
};
This last console.log returns the required data correctly!
For what I have been able to read the problem is related to the promise of the server request... but i am stuck!
I am not sure what was failing because i was receiving the expected data.
I think as someone mentioned it could be related to the manipulation of the response, delaying it...
In stead I added an event trigger that updates the array the typeahead attribute reads from and it now works fine. As well the typeahead-wait-ms is required cause my server response is between 20 and 30ms so just to be safe I set it to 200ms.
working code:
view: displays the values of the array "item"(item.theName == x.theName)
<input type="text" ng-model="selected" typeahead="x.theName for x in item" ng-change="dynamicSearch($viewValue, field.displayName)" typeahead-wait-ms="1000" class="form-control" typeahead-on-select='onSelect($item, field)'>
Controller functions:
On ng-change ->dynamicSearch() =>define what data request and call the request
$scope.dynamicSearch = function(searchTerm, name) {
var allowed = {};
var classString = "";
allowed = datamodel.classes[$routeParams.class].attribute[name].allowed;
for (key in allowed){
classString = classString + key + "|";
}
classString = classString.slice(0, -1);
$scope.getDbRefDocs(searchTerm, name, classString);
};
On call of getDbRefDocs() => i define values for the array "item"
$scope.getDbRefDocs = function(searchTerm, name, classString) {
var url = '/api/v2/docs/' + classString;
$http.get(url, {
params: {
'>displayName': searchTerm,
count: 5
}
}).then(function(response){
var data = response.data.data;
var requested = [];
angular.forEach(data.requested, function(searchTerm, k, o) {
requested.push(createDBOifNecessary(searchTerm));
});
$scope.item=[];
for (key in requested) {
if (requested.hasOwnProperty(key)) {
$scope.item.push({
'theName':requested[key].attributes.displayName[0],
'theVal':requested[key].cid
});
}
}
});
};
When item is selected from the available options of "item" => typeahead-on-select='onSelect($item, field)' => I store item.theVal:
$scope.onSelect = function (item, field) {
field.theValues[field.theValues.length] = item.theVal;
};

angular function in ng-show with a promise getting error

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>

Resources